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

残プロ 第-7回 ~pythonで文字列操作~

文字列strの操作 今回実装する操作は以下の2つです. 1.大文字で区切る ex)"EveryDay" → "Every" + "Day" 2.コンマ[,]で区切る ex)"one,two,three" → "one" + "two" + "three" サンプル.py 文字列str型に対するメソッドsplitと正規表現モジュールreのfindall関数を使用します. splitは区切り文字が含まれず,findallは含めることができます. sample.py import re def separateStr(string): separated_comma = string.split(',') if type(separated_comma) == list: separated_upper = [] for s in separated_comma: separated_upper.append(re.findall('[A-Z][a-z]+', s)) else: separated_upper = re.findall('[A-Z][a-z]+', separated_comma) return separated_upper
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

notion apiとpythonで本管理の備忘録

Notionの固有名詞知らないのでいい感じで 事前準備 pythonの環境設定 NotionのAPIキーの取得 対象ページにintegrationsを招待 対象ページのidをメモ Notionのテーブル?設定 こんな感じでTitle, Author, PublishedDate, ISBNを定義する. それぞれプロパティはtitle, text,text,numberにした. 流れ ISBN(13桁) --> google books api --> Notion API google books apiからはタイトル,著者名,出版日,ISBNを取得 Notion APIでPOSTする 使い方 python bookpost.py <13桁のISBN> コード プロパティの種類で書き方が違うことに注意. textはrich_textって書く. data=json.dumps(data)のとこを癖で日本語のエスケープ書くとpostでエラーになる. bookpost.py import json import sys import requests NOTION_ACCESS_TOKEN = YOUR_SECRET_KEY NOTION_PAGE_ID = YOUR_PAGE_ID def request_googleapi(isbn): # print(isbn) url: str = "https://www.googleapis.com/books/v1/volumes" try: response = requests.get(url, params={"q": "isbn" + isbn}) response.raise_for_status() except requests.exceptions.RequestException as e: print("error : ", e) print(response.text) exit(1) return response.json() def POST_dict(data): dic = {} # print(data) dic["title"] = data["items"][0]["volumeInfo"]["title"] dic["author"] = ", ".join(data["items"][0]["volumeInfo"]["authors"]) dic["date"] = data["items"][0]["volumeInfo"]["publishedDate"] tmp = data["items"][0]["volumeInfo"]["industryIdentifiers"] value_list = [x["identifier"] for x in tmp if x["type"] == "ISBN_13"] value = value_list[0] if len(value_list) else 0 dic["isbn"] = value # print(dic) return dic def notion_post(bo): url: str = "https://api.notion.com/v1/pages" try: headers = { "Authorization": NOTION_ACCESS_TOKEN, "Content-Type": "application/json", "Notion-Version": "2021-05-13", } data = { "parent": {"database_id": NOTION_PAGE_ID}, "properties": { "Title": { "type": "title", "title": [{"text": {"content": bo["title"]}}], }, "Author": { "type": "rich_text", "rich_text": [{"text": {"content": bo["author"]}}], }, "PublishedDate": { "type": "rich_text", "rich_text": [{"text": {"content": bo["date"]}}], }, "ISBN": {"type": "number", "number": int(bo["isbn"])}, }, } response = requests.post(url=url, headers=headers, data=json.dumps(data)) response.raise_for_status() except requests.exceptions.RequestException as e: print("error : ", e) print(response.text) exit(1) isbn = sys.argv[1] if len(isbn) != 13: exit(1) a = request_googleapi(isbn) bo = POST_dict(a) notion_post(bo) ちゃんと型とかつけたり初期化したいけど疲れた こーゆーの投稿するときトークンとかのせてないか不安になる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

yahoo乗り換え案内を抽出する

電車の乗り換え案内のAPIないかな? なかなか使い勝手のいいものがない… これは、過去に書いた記事と同じパターン… https://qiita.com/hirohiroto522/items/6ff29be1344be805ecb0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

yahoo路線乗り換え案内の情報を抽出したい

電車の乗り換え案内のAPIないかな? なかなか使い勝手のいいものがない… これは、過去に書いた記事と同じパターン… yahoo路線の運行情報を取得したい じゃあ解決策は、同じ! と思ったが 今回もBeautifulSoupを使ってスクレイピングしていこう! と思ったが、普通にYahoo路線で経路を検索したときの結果が表示されるHTMlがそこそこ複雑で解析がめんどくさそう もう少しシンプルなHTMLはないのかいろいろ探していると... あった! 印刷するボタンから飛べるページがとてもシンプル! さらに、URLもシンプル! https://transit.yahoo.co.jp/search/print?from=出発駅&flatlon=&to=到着駅 これはありがたい シンプルにURLパラメータに出発駅と到着駅を入れれば経路がわかる じゃあBeautiful Soupで解析していこう 必要なライブラリ等は、yahoo路線の運行情報を取得したいをみて # -*- coding: utf-8 -*- import requests from bs4 import BeautifulSoup import strip #出発駅の入力 departure_station = input("出発駅を入力してください:") #到着駅の入力 destination_station = input("到着駅を入力してください:") #経路の取得先URL route_url = "https://transit.yahoo.co.jp/search/print?from="+departure_station+"&flatlon=&to="+ destination_station print(route_url) #Requestsを利用してWebページを取得する route_response = requests.get(route_url) # BeautifulSoupを利用してWebページを解析する route_soup = BeautifulSoup(route_response.text, 'html.parser') #経路のサマリーを取得 route_summary = route_soup.find("div",class_ = "routeSummary") #所要時間を取得 required_time = route_summary.find("li",class_ = "time").get_text() #乗り換え回数を取得 transfer_count = route_summary.find("li", class_ = "transfer").get_text() #料金を取得 fare = route_summary.find("li", class_ = "fare").get_text() print("======"+departure_station+"から"+destination_station+"=======") print("所要時間:"+required_time) print(transfer_count) print("料金:"+fare) #乗り換えの詳細情報を取得 route_detail = route_soup.find("div",class_ = "routeDetail") #乗換駅の取得 stations = [] stations_tmp = route_detail.find_all("div", class_="station") for station in stations_tmp: stations.append(station.get_text().strip()) #乗り換え路線の取得 lines = [] lines_tmp = route_detail.find_all("li", class_="transport") for line in lines_tmp: line = line.find("div").get_text().strip() lines.append(line) #路線ごとの所要時間を取得 estimated_times = [] estimated_times_tmp = route_detail.find_all("li", class_="estimatedTime") for estimated_time in estimated_times_tmp: estimated_times.append(estimated_time.get_text()) print(estimated_times) #路線ごとの料金を取得 fars = [] fars_tmp = route_detail.find_all("p", class_="fare") for fare in fars_tmp: fars.append(fare.get_text().strip()) #乗り換え詳細情報の出力 print("======乗り換え情報======") for station,line,estimated_time,fare in zip(stations,lines,estimated_times,fars): print(station) print( " | " + line + " " + estimated_time + " " + fare) print(stations[len(stations)-1]) 上記コードで試しに横浜から豊洲の経路を調べると 出発駅を入力してください:横浜 到着駅を入力してください:豊洲 https://transit.yahoo.co.jp/search/print?from=横浜&flatlon=&to=豊洲 ======横浜から豊洲======= 所要時間:49分(乗車33分) 乗換:2回 料金:IC優先:628円 ['16分', '10分', '7分'] ======乗り換え情報====== 横浜 | 京急本線快特 16分 303円 品川 | JR山手線内回り 10分 157円 有楽町 | 東京メトロ有楽町線(和光市−新木場) 7分 168円 豊洲 いい感じだね! もう少しやってみたいこと 現状、日付や出発時間などの検索オプションをつけることができていない いずれも、URLパラメータを付与することで実現可能のように見える どのパラメータに何を入れればいいのか明らかにしたい 注意事項 スクレイピングはグレーなところもあるので、悪用厳禁!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WSLを使ってPythonの環境構築をする方法を簡単に解説

はじめに 環境を汚したくなかったので仮想環境でpythonを動かせるようにしました。 このトピックは多くの方が既に書かれていますが、私の環境下では検索上位の記事通りにやっても少しうまくいかなかったので、備忘録的な意味で記事にしています。 環境 Windows10 education 64bit 手順 WSLの有効化 1. "Windowsの機能の有効化または無効化"を開く 左下の検索窓に機能て打てば出てきます。 2. "Linux用Windowsサブシステム"にチェックを入れる チェックを入れてください。 そんな項目ないよって人は"Windows Subsystem for Linux"になってるかもしれないです。 チェックを入れてOKを押すと必要なもののインストールが始まります。 インストールが終わったら再起動しましょう。 Ubuntuの導入とセットアップ 1. Ubuntuのインストール Microsoft storeにてubuntuと検索します。 何個かでてきますが今回は"Ubuntu 18.04 LTS"をインストールします。20.04の方でも別に大丈夫です。(多分) 2. Ubuntuを起動 インストールできたら起動してください。 起動すると最初に色々インストールがされた後、ユーザー名とパスワードを設定するように言われるので従います。 3. パッケージの更新 以下のコマンドを実行し、パッケージを更新します。 ちなみに$マークは入力開始の目印なので、コピペするときは外してください。 $ sudo apt update $ sudo apt upgrade -y Pythonの導入 1. pyenvを入れる pyenvとはpythonのバージョン管理ツールです。 世界的にはvirtualenvの方がよく使われているらしいですが気にしません。(参考:pyenvが必要かどうかフローチャート) そもそも趣味レベルにおいてバージョンの切り替えなんて基本しないので、必要ない気もするんですが、初心者向けの本やサイトを見ると何故か入れさせたがるので、まぁ一応入れることにします。 まずビルド環境を整えます。Suggested build environmentに従い、以下のコマンドを実行します。 改行されてますが一行扱いなのでまとめてコピーして貼り付けてください。 sudo apt-get update; sudo apt-get install make build-essential libssl-dev zlib1g-dev \ libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \ libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev 途中で続行するか聞かれるので、yを押して続行しましょう。 次にgitをインストールします。 pyenvはパッケージでは提供されていないので、githubから取ってくる必要があります。以下のコマンドを実行してgitが使えるようにしましょう。 $ sudo apt-get install git 最後にpyenvをインストールします。 $ git clone https://github.com/pyenv/pyenv.git ~/.pyenv 2. pyenvの設定 必要な設定を~/.profileに追加します。~/.bashrcに記述する派の人もいますが、私にはいまいち違いが分かりません 以下のコマンドを実行します。 $ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.profile $ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.profile $ echo 'eval "$(pyenv init --path)"' >> ~/.profile 設定が追加できたら、以下のコマンドを実行して読み込みましょう。 $ source ~/.profile 以下のコマンドを実行して、インストール可能なバージョンの一覧が表示されればOKです。 $ pyenv install -list 3. Pythonのインストール 今回はPython 3.9.5を入れます。 $ pyenv install 3.9.5 続いて、今インストールした3.9.5に切り替えます。 $ pyenv global 3.9.5 下記のコマンドを実行して切り替わって入ればOKです。 $ python -V おまけ よく使うライブラリの導入 1. pipのインストール pipはpythonのパッケージを管理するツールです。 pythonを導入すると一緒についてくるので、今回はわざわざ何かする必要はありません。 下記のコマンドでバージョンが確認できます。 $ pip -V もし入っていない場合は下記のコマンドを実行してください。 $ sudo apt-get install python3-pip 2. ライブラリの導入 今回は私がよく使っているnumpy,pandas,matplotlibを入れます。 $ pip install numpy $ pip install pandas $ pip install matplotlib 終わりに 少しでも参考になれば幸いです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WSLを使ってPythonの環境構築する方法を簡単に解説

はじめに 環境を汚したくなかったので仮想環境でpythonを動かせるようにしました。 このトピックは多くの方が既に書かれていますが、私の環境下では検索上位の記事通りにやっても少しうまくいかなかったので、備忘録的な意味で記事にしています。 環境 Windows10 education 64bit 手順 WSLの有効化 1. "Windowsの機能の有効化または無効化"を開く 左下の検索窓に機能て打てば出てきます。 2. "Linux用Windowsサブシステム"にチェックを入れる チェックを入れてください。 そんな項目ないよって人は"Windows Subsystem for Linux"になってるかもしれないです。 チェックを入れてOKを押すと必要なもののインストールが始まります。 インストールが終わったら再起動しましょう。 Ubuntuの導入とセットアップ 1. Ubuntuのインストール Microsoft storeにてubuntuと検索します。 何個かでてきますが今回は"Ubuntu 18.04 LTS"をインストールします。20.04の方でも別に大丈夫です。(多分) 2. Ubuntuを起動 インストールできたら起動してください。 起動すると最初に色々インストールがされた後、ユーザー名とパスワードを設定するように言われるので従います。 3. パッケージの更新 以下のコマンドを実行し、パッケージを更新します。 ちなみに$マークは入力開始の目印なので、コピペするときは外してください。 $ sudo apt update $ sudo apt upgrade -y Pythonの導入 1. pyenvを入れる pyenvとはpythonのバージョン管理ツールです。 世界的にはvirtualenvの方がよく使われているらしいですが気にしません。(参考:pyenvが必要かどうかフローチャート) そもそも趣味レベルにおいてバージョンの切り替えなんて基本しないので、必要ない気もするんですが、初心者向けの本やサイトを見ると何故か入れさせたがるので、まぁ一応入れることにします。 まずビルド環境を整えます。Suggested build environmentに従い、以下のコマンドを実行します。 改行されてますが一行扱いなのでまとめてコピーして貼り付けてください。 sudo apt-get update; sudo apt-get install make build-essential libssl-dev zlib1g-dev \ libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \ libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev 途中で続行するか聞かれるので、yを押して続行しましょう。 次にgitをインストールします。 pyenvはパッケージでは提供されていないので、githubから取ってくる必要があります。以下のコマンドを実行してgitが使えるようにしましょう。 $ sudo apt-get install git 最後にpyenvをインストールします。 $ git clone https://github.com/pyenv/pyenv.git ~/.pyenv 2. pyenvの設定 必要な設定を~/.profileに追加します。~/.bashrcに記述する派の人もいますが、私にはいまいち違いが分かりません 以下のコマンドを実行します。 $ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.profile $ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.profile $ echo 'eval "$(pyenv init --path)"' >> ~/.profile 設定が追加できたら、以下のコマンドを実行して読み込みましょう。 $ source ~/.profile 以下のコマンドを実行して、インストール可能なバージョンの一覧が表示されればOKです。 $ pyenv install -list 3. Pythonのインストール 今回はPython 3.9.5を入れます。 $ pyenv install 3.9.5 続いて、今インストールした3.9.5に切り替えます。 $ pyenv global 3.9.5 下記のコマンドを実行して切り替わって入ればOKです。 $ python -V おまけ よく使うライブラリの導入 1. pipのインストール pipはpythonのパッケージを管理するツールです。 pythonを導入すると一緒についてくるので、今回はわざわざ何かする必要はありません。 下記のコマンドでバージョンが確認できます。 $ pip -V もし入っていない場合は下記のコマンドを実行してください。 $ sudo apt-get install python3-pip 2. ライブラリの導入 今回は私がよく使っているnumpy,pandas,matplotlibを入れます。 $ pip install numpy $ pip install pandas $ pip install matplotlib 終わりに 少しでも参考になれば幸いです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エクセルファイルから基準となる文字のセルを見つけてデータを読み込む

we shall find 'uko' and 'kare-' in a excel file. because it's my life. python import pandas as pd import numpy as np DATA_OFFSET_COL = 1 DATA_OFFSET_ROW = 2 INTERVAL = 11 UNCOL = 9 MAX_EVENT_NUM = 9 MAX__NUM_SEQUENCE = 5 df = pd.read_excel('pd_debug.xlsx') okng = df.iloc[:,UNCOL] event = df.iloc[:,UNCOL+DATA_OFFSET_COL] data = df.iloc[:,UNCOL+DATA_OFFSET_COL+1] ok_row_list = okng[(okng=='uko') | (okng=='kare-')].index for i, j in enumerate(ok_row_list): event_list = [] for eve in range(MAX_EVENT_NUM): event_index = DATA_OFFSET_ROW+j+eve if event_index >= len(event): break event_list.append(event[event_index]) if i==0: s = pd.Series(event_list) else: s = pd.concat([s, pd.Series(event_list)], axis=1) s.columns=np.arange(MAX__NUM_SEQUENCE) print(s)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

10種類の犬種を判別するアプリを作ってみた

はじめに ITに興味が湧き、初めは書籍、progateで学びましたが、もっと本格的に勉強したいことからプログラミングスクールに通うことにしました。 Aidemy Premium Planで画像認識を学び、 10種類の犬種を判別するアプリを作ってみました。 犬種はドーベルマン、フレンチブルドッグ、グレートデン、ポメラニアン、シーズー、シベリアンハスキー、ブラッドハウンド、パピヨン、パグ、スタンダードシュナウザーの10種類にしました。 目次 ・1.Stanford Dogs Datesetから必要な画像を取得 ・2.モデルの定義、学習 ・3.改善 ・4.最後に 1.Stanford Dogs Datesetから必要な画像を取得 画像のダウンロード Stanford Dogs Dateset からImagesをダウンロードします。 デスクトップ等好きな場所に保存をし、フォルダの解凍を行います。 解凍には「Lhaplus」を使用しました。 フォルダ内には120の犬種がありますが、今回は10種類なので新しく10種類のフォルダを作成しました。 そのフォルダをgoogleのmy-driveへアップロードします。 my-driveとGoogleColaboratoryを接続する 先ほどmy-driveにアップロードしたフォルダをGoogleColaboratoryで使用するには、my-driveとGoogleColaboratoryを接続する必要があります。 下記コードをGoogleColaboratory上で実行します。 from google.colab import drive drive.mount('/content/drive') 準備完了です。次はモデルを作成します。 2.モデルの定義、学習 インポート import os import cv2 import numpy as np import matplotlib.pyplot as plt from tensorflow.keras.utils import to_categorical from tensorflow.keras.layers import Dense, Dropout, Flatten, Input from tensorflow.keras.applications.vgg16 import VGG16 from tensorflow.keras.models import Model, Sequential from tensorflow.keras import optimizers from sklearn.model_selection import train_test_split #画像サイズの決定 image_size = 100 画像の取得 #フォルダの取得 path_Doberman = [filename for filename in os.listdir('/content/drive/MyDrive/Colab Notebooks/images3/Doberman') if not filename.startswith('.')] path_Frenchbulldog = [filename for filename in os.listdir('/content/drive/MyDrive/Colab Notebooks/images3/French_bulldog') if not filename.startswith('.')] path_GreatDane = [filename for filename in os.listdir('/content/drive/MyDrive/Colab Notebooks/images3/Great_Dane') if not filename.startswith('.')] path_Pomeranian = [filename for filename in os.listdir('/content/drive/MyDrive/Colab Notebooks/images3/Pomeranian') if not filename.startswith('.')] path_ShihTzu = [filename for filename in os.listdir('/content/drive/MyDrive/Colab Notebooks/images3/ShihTzu') if not filename.startswith('.')] path_Siberianhusky = [filename for filename in os.listdir('/content/drive/MyDrive/Colab Notebooks/images3/Siberian_husky') if not filename.startswith('.')] path_bloodhound = [filename for filename in os.listdir('/content/drive/MyDrive/Colab Notebooks/images3/bloodhound') if not filename.startswith('.')] path_papillon = [filename for filename in os.listdir('/content/drive/MyDrive/Colab Notebooks/images3/papillon') if not filename.startswith('.')] path_pug = [filename for filename in os.listdir('/content/drive/MyDrive/Colab Notebooks/images3/pug') if not filename.startswith('.')] path_standardschnauzer= [filename for filename in os.listdir('/content/drive/MyDrive/Colab Notebooks/images3/standard_schnauzer') if not filename.startswith('.')] img_Doberman = [] img_Frenchbulldog = [] img_GreatDane = [] img_Pomeranian = [] img_ShihTzu = [] img_Siberianhusky = [] img_bloodhound = [] img_papillon = [] img_pug = [] img_standardschnauzer= [] #画像の読み込み for i in range(len(path_Doberman)): img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/images3/Doberman/'+ path_Doberman[i]) img = cv2.resize(img,(image_size,image_size)) img_Doberman.append(img) for i in range(len(path_Frenchbulldog)): img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/images3/French_bulldog/'+ path_Frenchbulldog[i]) img = cv2.resize(img,(image_size,image_size)) img_Frenchbulldog.append(img) for i in range(len(path_GreatDane)): img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/images3/Great_Dane/'+ path_GreatDane[i]) img = cv2.resize(img,(image_size,image_size)) img_GreatDane.append(img) for i in range(len(path_Pomeranian)): img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/images3/Pomeranian/'+ path_Pomeranian[i]) img = cv2.resize(img,(image_size,image_size)) img_Pomeranian.append(img) for i in range(len(path_ShihTzu)): img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/images3/ShihTzu/'+ path_ShihTzu[i]) img = cv2.resize(img,(image_size,image_size)) img_ShihTzu.append(img) for i in range(len(path_Siberianhusky)): img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/images3/Siberian_husky/'+ path_Siberianhusky[i]) img = cv2.resize(img,(image_size,image_size)) img_Siberianhusky.append(img) for i in range(len(path_bloodhound)): img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/images3/bloodhound/'+ path_bloodhound[i]) img = cv2.resize(img,(image_size,image_size)) img_bloodhound.append(img) for i in range(len(path_papillon)): img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/images3/papillon/'+ path_papillon[i]) img = cv2.resize(img,(image_size,image_size)) img_papillon.append(img) for i in range(len(path_pug)): img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/images3/pug/'+ path_pug[i]) img = cv2.resize(img,(image_size,image_size)) img_pug.append(img) for i in range(len(path_standardschnauzer)): img = cv2.imread('/content/drive/MyDrive/Colab Notebooks/images3/standard_schnauzer/'+ path_standardschnauzer[i]) img = cv2.resize(img,(image_size,image_size)) img_standardschnauzer.append(img) X = np.array(img_Doberman + img_Frenchbulldog + img_GreatDane + img_Pomeranian + img_ShihTzu + img_Siberianhusky + img_bloodhound + img_papillon + img_pug + img_standardschnauzer) y = np.array([0]*len(img_Doberman) + [1]*len(img_Frenchbulldog) + [2]*len(img_GreatDane) + [3]*len(img_Pomeranian) + [4]*len(img_ShihTzu) + [5]*len(img_Siberianhusky) + [6]*len(img_bloodhound) + [7]*len(img_papillon) + [8]*len(img_pug) + [9]*len(img_standardschnauzer) ) モデルの構築~学習 #データの分類 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42,stratify=y) print(X_train.shape) print(y_train.shape) print(X_test.shape) print(y_test.shape) y_train = to_categorical(y_train) y_test = to_categorical(y_test) #モデルの構築 input_tensor = Input(shape=(image_size,image_size, 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="sigmoid")) top_model.add(Dropout(0.1)) top_model.add(Dense(64, activation="sigmoid")) top_model.add(Dropout(0.1)) top_model.add(Dense(32, activation="sigmoid")) top_model.add(Dropout(0.1)) top_model.add(Dense(10, activation='softmax')) model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output)) for layer in model.layers[:19]: layer.trainable = False model.compile(loss='categorical_crossentropy', #optimizer=optimizers.SGD(lr=1e-1, momentum=0.9), optimizer=optimizers.Adam(), metrics=['accuracy']) # 学習 #model.fit(X_train, y_train, batch_size=32, epochs=1, validation_data=(X_test, y_test)) #グラフ用 history = model.fit(X_train, y_train, batch_size=32, epochs=10, verbose=1, validation_data=(X_test, y_test)) score = model.evaluate(X_test, y_test, batch_size=32, verbose=0) print('validation loss:{0[0]}\nvalidation accuracy:{0[1]}'.format(score)) #acc, val_accのプロット plt.plot(history.history["accuracy"], label="accuracy", ls="-", marker="o") plt.plot(history.history["val_accuracy"], label="val_accuracy", ls="-", marker="x") plt.ylabel("accuracy") plt.xlabel("epoch") plt.legend(loc="best") plt.show() #モデルを保存 model.save("my_model.h5") Epoch 9/10 40/40 [==============================] - 2s 52ms/step - loss: 1.2049 - accuracy: 0.6599 - val_loss: 1.3478 - val_accuracy: 0.5902 Epoch 10/10 40/40 [==============================] - 2s 52ms/step - loss: 1.1095 - accuracy: 0.6998 - val_loss: 1.2896 - val_accuracy: 0.6011 validation loss:1.289649248123169 validation accuracy:0.6010928750038147 loss とは、0に近づくほど正解に近く、正解とどれくらい離れているかという数値です。 acc とは、1(100%)に近いほど正解に近い、正解率のことです。 val_loss と val_accはテストデータでの値です。 3.改善 上記のモデルの正解率は60%とあまり良くありませんでした。精度を上げる方法として、水増し等を用いて画像枚数を増やすことが挙げられます。 以下のコードを実行して水増しを行い、画像枚数を増やして学習させました。 import os import glob import numpy as np from keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array, array_to_img def draw_images(generator, x, dir_name, index): # 出力ファイルの設定 save_name = 'extened-' + str(index) g = generator.flow(x, batch_size=1, save_to_dir=output_dir, save_prefix=save_name, save_format='jpg') # 1つの入力画像から何枚拡張するかを指定 # g.next()の回数分拡張される for i in range(10): bach = g.next() if __name__ == '__main__': # 出力先ディレクトリの設定(適宜変更) output_dir = "/content/drive/MyDrive/Colab Notebooks/images3/standard_schnauzer/extended" if not(os.path.exists(output_dir)): os.mkdir(output_dir) # 拡張する画像群の読み込み images = glob.glob(os.path.join('/content/drive/MyDrive/Colab Notebooks/images3/standard_schnauzer', "*.jpg"))#ファイル名を指定 # 拡張する際の設定 generator = ImageDataGenerator( rotation_range=90, # 90°まで回転 width_shift_range=0.1, # 水平方向にランダムでシフト height_shift_range=0.1, # 垂直方向にランダムでシフト channel_shift_range=50.0, # 色調をランダム変更 shear_range=0.39, # 斜め方向(pi/8まで)に引っ張る horizontal_flip=True, # 垂直方向にランダムで反転 vertical_flip=True # 水平方向にランダムで反転 ) # 読み込んだ画像を順に拡張 for i in range(len(images)): img = load_img(images[i]) # 画像を配列化して転置a x = img_to_array(img) x = np.expand_dims(x, axis=0) # 画像の拡張 draw_images(generator, x, output_dir, i) 以下コードを実行し、モデルの学習を行いました。 import os import cv2 import tqdm import numpy as np import matplotlib.pyplot as plt from tensorflow.keras.utils import to_categorical from tensorflow.keras.layers import Dense, Dropout, Flatten, Input from tensorflow.keras.applications.vgg16 import VGG16 from tensorflow.keras.models import Model, Sequential from tensorflow.keras import optimizers from sklearn.model_selection import train_test_split image_size = 100 img_Doberman = [] img_Frenchbulldog = [] img_GreatDane = [] img_Pomeranian = [] img_ShihTzu = [] img_Siberianhusky = [] img_bloodhound = [] img_papillon = [] img_pug = [] img_standardschnauzer= [] for curDir, dirs, files in os.walk('/content/drive/MyDrive/Colab Notebooks/images3/Doberman'): print(curDir, dirs, files) for i in tqdm.tqdm(range(len(files))): file=files[i] img=cv2.imread(os.path.join(curDir,file)) img = cv2.resize(img,(image_size,image_size)) img_Doberman.append(img) for curDir, dirs, files in os.walk('/content/drive/MyDrive/Colab Notebooks/images3/French_bulldog/'): print(curDir, dirs, files) for i in tqdm.tqdm(range(len(files))): file=files[i] img=cv2.imread(os.path.join(curDir,file)) img = cv2.resize(img,(image_size,image_size)) img_Frenchbulldog.append(img) for curDir, dirs, files in os.walk("/content/drive/MyDrive/Colab Notebooks/images3/Great_Dane/"): print(curDir, dirs, files) for i in tqdm.tqdm(range(len(files))): file=files[i] img=cv2.imread(os.path.join(curDir,file)) img = cv2.resize(img,(image_size,image_size)) img_GreatDane.append(img) for curDir, dirs, files in os.walk('/content/drive/MyDrive/Colab Notebooks/images3/Pomeranian/'): print(curDir, dirs, files) for i in tqdm.tqdm(range(len(files))): file=files[i] img=cv2.imread(os.path.join(curDir,file)) img = cv2.resize(img,(image_size,image_size)) img_Pomeranian.append(img) for curDir, dirs, files in os.walk('/content/drive/MyDrive/Colab Notebooks/images3/ShihTzu/'): print(curDir, dirs, files) for i in tqdm.tqdm(range(len(files))): file=files[i] img=cv2.imread(os.path.join(curDir,file)) img = cv2.resize(img,(image_size,image_size)) img_ShihTzu.append(img) for curDir, dirs, files in os.walk('/content/drive/MyDrive/Colab Notebooks/images3/Siberian_husky/'): print(curDir, dirs, files) for i in tqdm.tqdm(range(len(files))): file=files[i] img=cv2.imread(os.path.join(curDir,file)) img = cv2.resize(img,(image_size,image_size)) img_Siberianhusky.append(img) for curDir, dirs, files in os.walk('/content/drive/MyDrive/Colab Notebooks/images3/bloodhound/'): print(curDir, dirs, files) for i in tqdm.tqdm(range(len(files))): file=files[i] img=cv2.imread(os.path.join(curDir,file)) img = cv2.resize(img,(image_size,image_size)) img_bloodhound.append(img) for curDir, dirs, files in os.walk('/content/drive/MyDrive/Colab Notebooks/images3/papillon/'): print(curDir, dirs, files) for i in tqdm.tqdm(range(len(files))): file=files[i] img=cv2.imread(os.path.join(curDir,file)) img = cv2.resize(img,(image_size,image_size)) img_papillon.append(img) for curDir, dirs, files in os.walk('/content/drive/MyDrive/Colab Notebooks/images3/pug/'): print(curDir, dirs, files) for i in tqdm.tqdm(range(len(files))): file=files[i] img=cv2.imread(os.path.join(curDir,file)) img = cv2.resize(img,(image_size,image_size)) img_pug.append(img) for curDir, dirs, files in os.walk('/content/drive/MyDrive/Colab Notebooks/images3/standard_schnauzer/'): print(curDir, dirs, files) for i in tqdm.tqdm(range(len(files))): file=files[i] img=cv2.imread(os.path.join(curDir,file)) img = cv2.resize(img,(image_size,image_size)) img_standardschnauzer.append(img) X = np.array(img_Doberman + img_Frenchbulldog + img_GreatDane + img_Pomeranian + img_ShihTzu + img_Siberianhusky + img_bloodhound + img_papillon + img_pug + img_standardschnauzer) y = np.array([0]*len(img_Doberman) + [1]*len(img_Frenchbulldog) + [2]*len(img_GreatDane) + [3]*len(img_Pomeranian) + [4]*len(img_ShihTzu) + [5]*len(img_Siberianhusky) + [6]*len(img_bloodhound) + [7]*len(img_papillon) + [8]*len(img_pug) + [9]*len(img_standardschnauzer) ) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42,stratify=y) #rand_index = np.random.permutation(np.arange(len(X))) print(X_train.shape) print(y_train.shape) print(X_test.shape) print(y_test.shape) y_train = to_categorical(y_train) y_test = to_categorical(y_test) input_tensor = Input(shape=(image_size,image_size, 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="sigmoid")) top_model.add(Dropout(0.1)) top_model.add(Dense(64, activation="sigmoid")) top_model.add(Dropout(0.1)) top_model.add(Dense(32, activation="sigmoid")) top_model.add(Dropout(0.1)) top_model.add(Dense(10, activation='softmax')) model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output)) for layer in model.layers[:19]: layer.trainable = False model.compile(loss='categorical_crossentropy', #optimizer=optimizers.SGD(lr=1e-1, momentum=0.9), optimizer=optimizers.Adam(), metrics=['accuracy']) # 学習 #model.fit(X_train, y_train, batch_size=32, epochs=1, validation_data=(X_test, y_test)) #グラフ用 history = model.fit(X_train, y_train, batch_size=32, epochs=10, verbose=1, validation_data=(X_test, y_test)) score = model.evaluate(X_test, y_test, batch_size=32, verbose=0) print('validation loss:{0[0]}\nvalidation accuracy:{0[1]}'.format(score)) #acc, val_accのプロット plt.plot(history.history["accuracy"], label="accuracy", ls="-", marker="o") plt.plot(history.history["val_accuracy"], label="val_accuracy", ls="-", marker="x") plt.ylabel("accuracy") plt.xlabel("epoch") plt.legend(loc="best") plt.show() #モデルを保存 model.save("my_model.h5") (8397, 100, 100, 3) (8397,) (3599, 100, 100, 3) (3599,) Epoch 9/10 263/263 [==============================] - 13s 49ms/step - loss: 1.1344 - accuracy: 0.6052 - val_loss: 1.1568 - val_accuracy: 0.5949 Epoch 10/10 263/263 [==============================] - 12s 48ms/step - loss: 1.0829 - accuracy: 0.6255 - val_loss: 1.1571 - val_accuracy: 0.6046 validation loss:1.1570754051208496 validation accuracy:0.604612410068512 精度は60%と画像枚数を増やしても向上は見られませんでした。 4.最後に 初めは20%くらいだった精度を画像枚数を増やすことなく、「optimizer」や「lr」を変えることで向上させることができました。 それでも60%と十分な精度は得られず、画像枚数が原因ではないかと考え、水増しにより増やして改善を図りましたが、精度の向上は見られませんでした。 苦労した点は、精度向上と画像読込です。画像読込では「/」一つ抜けているだけで上手く読み込めず、どこにエラーがあるのかも詳しく表示されないので困りました。地道な作業と細かい箇所にも目を通すことの大切さを再認識しました。 今後は90%の精度を目指して、勉強を続け、知識を増やしていきたいと思います。 そしてなにより、Aidemyの素晴らしいチューターのサポートがあったからこそ最後まで楽しく作ることができたと思います。 アプリの作成だけでなく、指導方法等さまざまなことを学ばせていただきました。 三ヶ月間本当にありがとうございました!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python コードゴルフテクニック集

はじめに CodinGameというコードゴルフで競い合えるサイトがあるのですが、そこで学習した文字数を減らすテクについて解説します。 順番が非常に雑です。 空白 削れる空白は積極的に削っていきましょう。 old n = int(input()) print(n * 2) new n=int(input()) print(n*2) 数字と文字の間も削れるところは削れたりします。 print(1if"hoge"==input()else 0) 変数 1文字の変数はa-z,A-Zの52個あります。変数は基本的に1文字にしましょう。 old Answer=0 for i in range(5):Answer+=int(input()) print(Answer) new A=0 for i in range(5):A+=int(input()) print(A) 演算子 演算子には優先順位がありますが、優先順位が同じ演算子は基本的に左から計算します。 old N=int(input()) print(3*(N//3)) new N=int(input()) print(N//3*3) このようにして括弧を減らしていきましょう。 入力 変数に渡さない 入力値が必要ない場合は、単に input() として無視しましょう。 一回だけ入力値を必要とする場合は、式の中に input() を入れ込むと変数の長さを削減できます。 example #1行目に文字列の長さが渡されて、2行目の文字列のASCIIコードの値の合計を計算 input() print(sum(ord(i)for i in input())) 出力 リストのアンパック *記号を用いると、リスト内の値を全て出力するときに便利です。 オプションsepの値で区切ります。 example print(*map(len,input().split()),sep="a") #入力例 think sinking thonking #[5,7,8]となるので #出力例 5a7a8 改行無し 改行したくない場合は、print()関数のオプションであるendを使いましょう。 example print(end="hoge") f文字列(フォーマット済み文字列リテラル) f文字列は大変便利です。ここではコードゴルフで主に用いるものを挙げていきます。 穴埋め f文字列の基本です。中括弧{}で囲ったところに整数、文字列、変数を埋め込みます。 {}のなかに文字列を入れたいときは、シングルクォーテーション'とダブルクォーテーション"を使い分けましょう。 example #左の数値が右の数値より大きいかどうか A,B=map(int,input().split()) print(f"{A} is {'not '*(A<=B)}bigger than {B}") ゼロ埋め example #時刻表示 second = int(input())%86400 print(f"{second//3600:02}:{second%3600//60:02}:{second%60:02}") 進数変換 example #先程の0埋めとも組み合わせられます print(f"{int(input()):b}")#2進数 print(f"{int(input()):o}")#8進数 print(f"{int(input()):08x}")#16進数で8桁まで0埋め 整数 1文字の不等号 >= より > 、<= より < を使った方が短いです。 old if n<=7:print("Yes") new if n<8:print("Yes") +1,-1 n+1 は -~n 、n-1 は ~-n と表せます。 文字数変わらないのに何が嬉しいんだよというと、- と ~ は */% より優先順位が高いので括弧をつける必要がなくなります。 old print((n+1)%3) new print(-~n%3) 切り上げ n/m を切り上げた整数は、math.ceil(n/m) と書くよりも (n+m+1)//m とした方が短く、さらにmathモジュールをimportする必要がなくなります。 old import math print(math.ceil(n/m)) new print((n+m-1)//m) bool値も整数 True は 1 、False は 0 にあたります。 example A=[9,9,8,2,4,4,3,5,3] print(sum(i>=5for i in A))#4 文字列 YNeos example print("YNeos"[(0か1)::2]) で短くできます。これは  other print(["Yes","No"][0か1]) よりもみじk・・・あれ? しかしこの手法が本領発揮するのは3つ以上を場合分けする時です。 例えば、 nを3で割った余りで think,thonk,sink を出力仕分けたいときは、 old print(["think","thonk","sink"][n%3]) とするよりも、 new print("ttshhiionnnkkk"[n%3::3]) とするとかなりの文字数削減です。この場合は、 newofnew print("ttshhiio"[n%3::3]+"nk") とすれば更に短くできます。 open(0) 挙動が少し難しいですがこれを使いこなせると時に数十bytesの削減になります。 まずは単純に code A = open(0) print(A) こうすると、標準入力にかかわらず出力は、 output <_io.TextIOWrapper name=0 mode='r' encoding='UTF-8'> と出てきて、は?となります。 ところが左辺(A)のところを2変数にすると挙動が変わってきます。 code A,B = open(0) print(A) print(B) そして標準入力を input attack guard とします。 標準出力は次のようになります。 output attack guard これは、変数Aには "attack\n", Bには "guard" が格納されていたことを表します。 左辺が2変数以上だと、左辺=open(0)は次のような処理をします。 標準入力を全て読み込む (今回は "attack\nguard" ) \nで区切ってリストにする。この時末尾の改行は残る。 ( "attack\nguard" → ["attack\n","guard"] ) 各変数に順番に代入する (A = "attack\n", B = "guard") ここで、アスタリスクを用いたアンパック代入を用いることで、 $1$行目に $N$ が与えられ、$2$ ~ $N$+$1$行目に整数が一つづつ与えられる という超典型標準入力を、 code N,*L = map(int,open(0)) で簡単に書き表すことができます。 しかし、次のような入力形式だと困ってしまいます。 $1$行目に $N$ が与えられ、$2$行目に$N$個の整数が空白区切りで与えられる open(0) は改行で区切るので同じようには上手くいきません。 それでも、open(0).read().split()が使えます。 open(0).read()で標準入力全てをstr型として受け取れるので、split()で空白and改行区切りできます。 code N,*L = map(int,open(0).read().split()) 数式変形 いきなり例題。 $N^0,N^1,\cdots N^M$ の総和を出力せよ。 単純に書くと次のようになります。 old N,M=map(int,input().split()) print(sum(N**i for i in range(M))) しかし、このような単純な総和を簡潔に表せないのは直感的に嫌な気分になります。 そこで、この数式を†Wolfram Alpha†に突っ込みます。 ということで、 new N,M=map(int,input().split()) print(~-N**M//~-N) おわりに 量が少なすぎる 気が向いたら追加
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python勉強記録 #5 Matplotlib, Seaborn

Matplotlib と Seaborn Matplotlibはデータの可視化によく使われるライブラリです。大小、傾向、分布、相関などを目視する時に用います。 SeabornはMatplotlibが内部で動いているのですが、特に、手軽に美しく可視化ができるライブラリです。 import方法 import matplotlib.pyplot as plt import seaborn as sns と書いて読み込みます。 Matplotlibで棒グラフを作ってみる # dfを定義、列名A,B,C 行名ONE,TWO,THREE df = pd.DataFrame({'A':[2,2,3], 'B':[4,5,6], 'C':[7,8,9]}, index=[1,2,3]) print(df) """ 出力結果 : A B C 1 2 4 7 2 2 5 8 3 3 6 9 """ # value_counts()を使ったら test = df['A'].value_counts() print(test) """ 出力結果 : 2 2 3 1 Name: A, dtype: int64 """ # 横軸の値 x = df['A'].value_counts().index # 縦軸の値 height = df['A'].value_counts().values print(x,height) """ 出力結果 : Int64Index([2, 3], dtype='int64'), array([2, 1]) """ value_countsメソッドは、カテゴリ変数の各クラスがそれぞれいくつあるかを数え上げてくれます。ユニークな要素の値がindex、その出現個数がdataとなるpandas.Seriesを返すので、要素の頻度(出現回数)が必要な場合はこちらを使います。 testを出力したら、「2が2つ、3が1つですよ」と返してくれているのがわかります。 value_counts.indexでtestの中の、indexをpandas.core.indexes.numeric.Int64Indexという型で手に入れられます。 value_counts.valueでそのindexの要素の出現回数を、numpy.ndarray型で手に入れられます。 これをmatplotlibで図式化すると、 # 横軸に x , 縦軸に height の棒グラフを描画。 plt.bar(x, height) このようなグラフが出来上がります。 Seabornを使うと # Seabornのスタイルを使う plt.style.use('seaborn') # 横軸に x , 縦軸に height の棒グラフを描画。 plt.bar(x, height) このようにちょっと見やすくなりました。 折れ線グラフ plt.plot(x, y)で折れ線グラフが描画できます。 ヒストグラム plt.hist(df['A'], bins=10)10分割にして分布をみるためのヒストグラムを作れます。 Seabornを使った場合 sns.distplot(df['A'], kde=False, bins=10ヒストグラムの背景に目盛りがつきます。 kde=Trueにすると、確率密度関数が表示されます。 箱ひげ図 plt.boxplot(df['A'])外れ値を検討する時に使える箱ひげ図を表示できます。 散布図 plt.scatter(df['A'], df['B'])X軸のデータをA列から、Y軸のデータをB列から取った散布図を描画できます。A列のデータとB列のデータの相関関係を可視化できます。 sns.pairplot(df)各列と各列をそれぞれX軸、Y軸に取った散布図を全て表示し、一度に確認することができます。 ヒートマップ df_corr = df_corr()全ての組み合わせの相関係数を取得し、df_corrに代入します。 sns.heatmap(df_corr, annot=True, cmap='Blues') annotはTrueにすることで、相関係数を表示することができます。 cmapではヒートマップの色系統を指定できます。 要約統計量の確認 df.describe()では、要素の数、平均値、中央値、最小値、最大値などを一覧でみることができます。 グループ化 任意のカテゴリカル変数についてグルーピングを行うことができます。 df.groupby('A').mean()では、A列の要素によって、グループ分けをして、それぞれのグループごとに、他の列の値の平均値を取得できます。 クロス集計 pd.crosstab(df['A'], ['B'])A列とB列のカテゴリカル変数を掛け合わせてサンプル数を集計します。データフレームが表示され、データフレームの値にはサンプル数が表示されます。 ピボットテーブル pd.pivot_table(df, values='A', index='B', columns='C', aggfunc='mean') データフレームの値は、A列から。agghunc='mean'によって、その平均が表示されます。 カラムはC列の要素から、インデックスは、B列の要素から表示されます。 データの中身についてよく理解した上で、こういったものを役立てるといいかもしれません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

文字列の時間を分とか秒へ変換したい

やりたいこと Youtubeのコメントによくある、「5:11 めっちゃおもろいw」のようなコメントから 5:11の部分を持ってきて、秒数などに変換したい。 どうすればいいか 文字列の時間(1:38:30みたいなの)から時、分、秒を取ってくる必要がある。 2:23:11→2時間23分11秒 1:34→1分34秒 同じような表記なのでどこが時で分で秒か?ということを考慮する必要がある。 調べてみると、文字列→時間に変換するには、 strftime(),strptime()を使うことでできるらしい。 使用例: import datetime tt = datetime.datetime.strptime('1:23:32', '%H:%M:%S').strftime('%H:%M:%S') print(tt) 01:23:32 時、分、秒を別々に取得するには cmt = '2:4:22' hour = datetime.datetime.strptime(cmt, '%H:%M:%S').strftime('%H') min = datetime.datetime.strptime(cmt, '%H:%M:%S').strftime('%M') sec = datetime.datetime.strptime(cmt, '%H:%M:%S').strftime('%S') print(int(hour) * 60 * 60) print(int(min) * 60) print(sec) このようにすれば、それぞれの秒数を取得できる、 注意点は、文字列を格納しているcmtの表記が分:秒であるときはエラーになるので、 分:秒ように分岐させる必要あり。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ImageJでOlympusViewerを用いてVSIファイルをTiffにする奴

背景 僕の研究室ではオリンパスの蛍光顕微鏡を使ってオリンパスのソフトで撮影して出力している。撮影したときのファイルがVSIファイルで、本来ImageJ等では扱えないファイル。 でも、それを可能にしてくれているのがFijiにも付属しているOlympusViewerというプラグインである。 上記のオリンパスのソフトでTiff出力をしたい所だが、なんか上手くいかないのでVSIファイルをImageJ(Fiji)上でTiffに変換して保存する方がその後の使い勝手が良いだろう、と言うことでちょっと開発した。 コード このコードはFijiのエディタで動かせるのでFijiでRunをクリックして使ってほしい。 SaveAsTiff_fromVSI from ij import IJ #Run Olympus viewer IJ.run("Options", " ") IJ.run("Viewer", "open=") imp = IJ.getImage() #Save as tiff if IJ.saveAs(imp, "Tiff", ""): imp.close() else: imp.close() 内容がメチャクチャ簡単だけど一応解説。 from ij import IJ なんか必要な奴。モジュールのインポートに必要。 #Run Olympus Viewer IJ.run("Options", " ") #OlympusViewerの設定画面を起動、チェックを二つとも外す IJ.run("Viewer", "open=") #Viewerを起動 imp = IJ.getImage() #開いた画像をimpという変数に収納 2行目の"open="の=の先に絶対パスを置けば画像を指定で開けるし、何も書かなければ選択画面が表示される。 #Save as tiff if IJ.saveAs(imp, "Tiff", ""): imp.close() else: imp.close() すごく怪しい部分。 画像を開いた後立て続けに保存画面が出てくるが、名前をつけて保存を押せば勝手にTiffファイルで保存され、開いた画像は閉じられる。 一方、保存ボタンが押されなければそのままだとエラーを吐いてプログラムが止まるので、保存しなかったときにもプログラムが正常終了するようにした。メジャーな書き方では無いと思う。 ちなみにそもそも画像を開かなかったときはエラーを吐いて止まる。 やりたかったけど出来なかったこと ○勝手に保存するシステム 保存画面で名前をつける行程すら省きたかったが、そもそもそんな必要が無い(手作業で名前つける方が色々効率的)なのと、そんな技術が無かった。 開いた画像のpathを取得する方法がわからない、それが出来ると次のアプリ開発も上手くいくんだけどナァ…
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

機械学習でツイートが炎上するか予測してみた

1. 本記事について Twitter上では時折、投稿した内容に対して多くの人の非難が集まる いわゆる"炎上"してしまうケースが起こります。 本記事ではツイートの"文章の特徴"に基づいて、 投稿する内容が炎上するかどうかを機械学習で予測を 行った手法および結果を記載します。 2. 成果物 今回作成した予測モデル(3章以降で説明)については Webサービスにして利用できるようにしてみました。 炎上度チェッカー (いらすとや先生の素材をお借りしました。) まだβ版的な感じですが良ければ遊んでみてください。鳥が燃えたらクリアです。 ・使用イメージ ↓ 3. 炎上予測モデル作成の流れ 以下の図のようにデータ収集+前処理を実施し、 機械学習を用いたテキストの分類タスクに落とし込んでみます。 ネガティブな反応の多いグループと少ないグループに含まれるツイートでは 文章の特徴に異なった傾向があるのではないか、という仮説です。 2項分類でも良いかもしれないですが、段階的に評価できた方が面白そうなので このようにしました。 4. モデル作成手順 4.1 データ準備 4.1.1 データ収集 こちらに投稿したコードを使って「一定以上の反応があったツイート」と、 「上記ツイートについたリプライ」のセットをcsvファイルにして収集しました。 今回使用したデータは以下の通りです。 期間:2020/5/20 ~ 2021/4/30 件数:ツイート 約48万件 / リプライ 約1800万件 対象ツイート: ・日本語での投稿 ・投稿から2日後時点で、30件以上のリプライがついている ・画像、動画、URLリンク付きでない 以下の検索文字列をtweepyの引数に渡しています。 ツイート収集用:"lang:ja -filter:links exclude:retweets min_replies:30" リプライ収集用:"lang:ja -filter:links filter:replies exclude:retweets" なお、Twitter APIの取得上限に引っかかったりしているため、 期間内の条件に当てはまるツイート全てを収集できてはいません。 4.1.2 データロード 収集したcsv群をまとめてDataFrameにします。 ツイート→tweet_df、リプライ→reply_dfにロードします。 import pandas as pd import csv import glob # csvディレクトリのパスを取得 DATA_PATH = "/csv_directory" All_Files = glob.glob('{}tweet*'.format(DATA_PATH)) # ディレクトリ内の全csvをマージ tweets = [] for file in All_Files: tweets.append(pd.read_csv(file,engine='python')) tweet_df = pd.concat(tweets, sort=False) DataFrameは以下のカラム構造になっています。 ・id (ツイートごとに一意のもの) ・to_id (宛先ツイートID ※リプライのみ) ・created_at (投稿日時) ・user (投稿したユーザー名) ・text (ツイート内容) 4.1.3 データ整形 ・欠損値削除 tweet_df.dropna() ・ツイート文章内の絵文字、顔文字、@ユーザー名削除 import emoji import nagisa import unicodedata import re # 絵文字削除用関数 def remove_emoji(src_str): return ''.join(c for c in src_str if c not in emoji.UNICODE_EMOJI) # 顔文字削除関数 def remove_kaomoji(text): KAOMOJI_LEN = 5 results = nagisa.extract(text, extract_postags=['補助記号']) words = results.words kaomoji_words = [] kaomoji_idx = [i for i, w in enumerate(words) if len(w) >= KAOMOJI_LEN] kaomoji_hands = ['ノ', 'ヽ', '∑', 'm', 'O', 'o', '┐', '/', '\\', '┌'] # 顔文字と手を検索 for i in kaomoji_idx: kaomoji = words[i] try: # 顔文字の左手 if words[i-1] in kaomoji_hands and 0 < i: kaomoji = words[i-1] + kaomoji # 顔文字の右手 if words[i+1] in kaomoji_hands: kaomoji = kaomoji + words[i+1] except IndexError: pass finally: kaomoji_words.append(kaomoji) # 抽出した顔文字を削除 for j in kaomoji_words: text = text.replace(j, '') return text # 整形したテキストをDataFrameに追加 tweet_text = [] for text in tweet_df["text"]: text = remove_emoji(text) #絵文字削除 text = remove_kaomoji(text) #顔文字削除 text = re.sub(r'@[0-9a-zA-Z_:]*', "", text) #@ユーザー名削除 tweet_text.append(text) tweet_df["text_modified"] = tweet_text ※reply_dfにも同じ処理をします。 4.1.4 リプライの感情分析 テキストの感情分析ができるパッケージやAPIは多く存在しますが、 大半がPositive/Negativeの2軸での評価になっています。 しかし、Negativeな感情のうち、"悲しい"などは今回の炎上するか、 という観点からはズレていると考えられます。 ※ 例えば著名人の訃報を知らせるツイートに対するリプライは "悲しい"を中心としたNagativeな感情のスコアが高くなると想定されますが、 炎上とは異なるため なのでこれらを除外するため、"嬉しい","悲しい","怒り","嫌悪感","驚き","恐怖"の 6種類で感情の評価ができるこちらのパッケージをお借りし、"怒り"と"嫌悪感"のスコアに注目して予測処理を行っていきます。 先ほどのデータ整形と同じように感情分析スコアを "reply_df"に追加していき、以下のようにします。 各感情スコアの取り得る値の幅は0~100です。 宛先ツイートごとに感情分析スコアを集計します。 reply_groupby_df = reply_df.groupby('to_id').sum() 4.1.5 ツイートとリプライデータの結合 ツイートの文章と、集計したリプライの感情スコアがセットになるよう結合します。 tweet_reply_df = pd.merge(tweet_df, reply_groupby_df, left_on='id',right_on='to_id', how='inner') 4.2 データの確認 ツイートごとの感情スコアを確認します。 ・統計値 ・angry, disgustの分布 4.3 クラス分け ここからangryとdisgustの値に基づいて、ツイートのクラス分けを行います。 angryとdisgustの値の和を取ってパーセントタイルで分けようかと考えていましたが、 先ほどの値の分布を見るとクラスタリングした方が良さそうです。 今回はk-meansでクラスタリングしてみます。 ただ、このままでは右上の外れ値の数件でクラスタが できてしまいそうなので、min-max normalizationで値の正規化を行います。 tweet_reply_df["angry_mmn"] = (tweet_reply_df["angry"]-tweet_reply_df["angry"].min()) / (tweet_reply_df["angry"].max()-tweet_reply_df["angry"].min()) tweet_reply_df["disgust_mmn"] = (tweet_reply_df["disgust"]-tweet_reply_df["disgust"].min()) / (tweet_reply_df["disgust"].max()-tweet_reply_df["disgust"].min()) 正規化したangry,disgustの値を元に、エルボー法でクラスタ数の見積りを行います。 from sklearn.cluster import KMeans distortions = [] for i in range(1,11): # 1~10クラスタまで計算 km = KMeans(n_clusters=i, init='k-means++', n_init=10, max_iter=300, random_state=0) km.fit(tweet_df[['disgust_mmn', 'angry_mmn']]) distortions.append(km.inertia_) plt.plot(range(1,11),distortions,marker='o') plt.xlabel('Number of clusters') plt.ylabel('Distortion') plt.show() 以下のような結果になりました。 曲線が急に下るあたりが適切なクラスタ数です。 3くらいが良さそうですが、今回は5で進めてみます。 正規化したangry,disgustの値でクラスタリングします。 # k-meansでdisgust,angryをもとに分類 kmeans_model = KMeans(n_clusters=5, random_state=0).fit(tweet_df[['disgust_mmn', 'angry_mmn']]) # 分類結果のラベルをDataFrameに追加 tweet_df["label"] = kmeans_model.labels_ クラスタリング結果です。 ・批判的なリプライの最も多いクラスタのツイートを見てみる (label=5) ・批判的なリプライが最も少ないクラスタのツイートを見てみる (label=1) 目視で確認する限りですが、文章の特徴に有意な差異がありそうな感じがします。 大きく炎上したと思われるツイートには以下のような特徴が見受けられました。 ・主語が大きい内容 (日本は~、男は~…etc) ・政治、性差、国民性、コロナ関連への言及 ・不祥事についての謝罪 4.4 ツイート文章のベクトル化 ツイートが上記のどのクラスに所属するかを予測するにあたって、 ツイート文章の特徴ベクトルを取得します。 今回はベクトル化の手法としてBERTを使用します。 日本語学習済のBERTを使ったこちらのコードをお借りしました。 ※ コード内の"sample_df"を"tweet_df"に置き換えて実行しています。 BSV = BertSequenceVectorizer() tweet_df['text_feature'] = tweet_df['text_modified'].progress_apply(lambda x: BSV.vectorize(x)) ※参考:自然言語処理の王様「BERT」の論文を徹底解説 4.5 機械学習モデリング 学習データとテストデータに9:1で分割します。 from sklearn.model_selection import train_test_split feature_train, feature_test,label_train, label_test, weight_train, weight_test = train_test_split(tweet_df["text_feature"],tweet_df["label"], weight_list, test_size=0.1, shuffle=True) モデルについてはいくつかアルゴリズムを試してみた中で、 一番精度が良かったXGBoostを使います。 ※ クラスごとのサンプルサイズに偏りがあるため、sample_weight=...の箇所で サンプルサイズに応じた重み付けを行っています。 import xgboost as xgb from sklearn.model_selection import GridSearchCV from sklearn.datasets import load_digits # モデル定義 xgb_mc = xgb.XGBClassifier() # ハイパーパラメータ探索 xgb_model_cv = GridSearchCV(xgb_mc, {'eta': [0.01,0.1,0.3], 'max_depth': [5,6,7,8,9,10], 'gamma': [0.01,0.1,1.0,10.0]}, verbose=1, n_jobs=-1, cv=5) # 学習 xgb_model_cv.fit(feature_train, label_train, sample_weight=weight_train) print(xgb_model_cv.best_params_, xgb_model_cv.best_score_) 4.6 モデル評価 分類精度を見てみます。 from sklearn.metrics import confusion_matrix, classification_report pred = xgb_model_cv.predict(feature_test) print(classification_report(label_test, pred)) ラベル precision recall f1-score support 1 0.51 0.60 0.55 5011 2 0.47 0.38 0.42 5023 3 0.34 0.29 0.31 2587 4 0.09 0.14 0.11 389 5 0.05 0.33 0.09 39 指標 accuracy 0.44 13049 macro avg 0.29 0.35 0.30 13049 weighted avg 0.45 0.44 0.44 13049 全体のaccuracyは44%。大きく炎上したラベル4, 5の サンプルサイズがもっとあると良くなりそうです。 また、今回は段階的なクラス分けになっているため、一応 ニアピンで外しているものも正解とした場合の精度もみてます。 result = sum(1*(p == t or p == t+1 or p==t-1) for p, t in zip(pred.tolist(), label_test)) / len(label_test) print("Accuracy: {:.2f}".format(result)) Accuracy: 0.89 炎上する傾向がある程度は掴めている感じがします。 炎上したツイートをもっと効率的に集められると良いのですが Twitterで検索文字列に ":(" を入れるネガティブ検索は 普通に顔文字として検索されているようで上手く機能していないようでした。 5. chatbot作成 Webサービス化するにあたって、分類結果を返すだけだと味気ないので、 想定されるリプライも返すようchatbotを作ってみました。 アプリ自体はdjangoを使って作っています。 5.1 会話データ準備 なるべく辛辣なリプライを返してくれた方が面白いかなと思ったので、 以下のように各ツイートへのリプライから"怒り"と"嫌悪感"のスコアの和が 最も高いリプライのみを抽出し、これを学習させる会話データとしました。 5.2 会話データの学習 Tensor2Tensorを使いました。 Tensor2Tensorを使ったchatbotの作成については 検索により手順が大量に出てくるためここでは割愛します。 以下のあたりを参考にさせて頂きました。 Tensor2Tensor Documentation Tensor2Tensorで雑談チャットボットを作ったら今度はうまくいった話 あとがき 今回は炎上ツイートにフォーカスして予測をしてみましたが 炎上した内容でも何らかの課題提起になっていたりするので 必ずしも炎上=悪いということでは無いと思います。 ただ、投稿するツイートに対する反応をある程度予測できることに 価値があるのではないかと考えてやってみました。 また、今回は単にリプライの文章に対して感情分析をかけただけで ツイートとリプライ間の文脈の考慮ができていません。 そのため、"ネガティブなワードを含む同意"が多いツイートを炎上する 可能性が高いと判定してしまっているケースがあったりします。 例) 「〇〇ひどいな」 ← 「ほんと最悪」 今後の展望 全体的にまだ拙い部分が多いと思いますので まとまった時間が取れたら見直してみる予定です。 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

無線局等情報検索のWeb-APIから楽天モバイルの包括免許を取得する

Web-APIで各通信局ごとに取得し都道府県別・市区町村別にデータラングリング 例)四国総合通信局内の(香川・徳島・愛媛・高知)の四県の市町村別にまとまって表示される Web-API 完成 各通信局別 https://github.com/imabari/musen/tree/gh-pages 更新状況 https://imabari.github.io/musen/updated.csv プログラム import csv import datetime import pathlib import time import urllib.parse import pandas as pd import requests api = { # 1:免許情報検索 2: 登録情報検索 "ST": 1, # 詳細情報付加 0:なし 1:あり "DA": 1, # スタートカウント "SC": 1, # 取得件数 "DC": 1, # 出力形式 1:CSV 2:JSON 3:XML "OF": 1, # 無線局の種別 "OW": "FB_H", # 所轄総合通信局 "IT": "A", # 免許人名称/登録人名称 "NA": "楽天モバイル", } musen = [ {"auth": "01_hokkaido", "value": "J"}, {"auth": "02_tohoku", "value": "I"}, {"auth": "03_kanto", "value": "A"}, {"auth": "04_shinetsu", "value": "B"}, {"auth": "05_hokuriku", "value": "D"}, {"auth": "06_tokai", "value": "C"}, {"auth": "07_kinki", "value": "E"}, {"auth": "08_chugoku", "value": "F"}, {"auth": "09_shikoku", "value": "G"}, {"auth": "10_kyushu", "value": "H"}, {"auth": "11_okinawa", "value": "O"}, ] df_code = pd.read_csv( "https://docs.google.com/spreadsheets/d/e/2PACX-1vSseDxB5f3nS-YQ1NOkuFKZ7rTNfPLHqTKaSag-qaK25EWLcSL0klbFBZm1b6JDKGtHTk6iMUxsXpxt/pub?gid=284869672&single=true&output=csv", dtype={"団体コード": int, "都道府県名": str, "郡名": str, "市区町村名": str}, ) df_code["市区町村名"] = df_code["郡名"].fillna("") + df_code["市区町村名"].fillna("") df_code.drop("郡名", axis=1, inplace=True) df_code def fetch_api(parm, auth): r = requests.get("https://www.tele.soumu.go.jp/musen/list", parm) r.raise_for_status() cr = csv.reader(r.text.splitlines(), delimiter=",") data = list(cr) # 更新日 update = datetime.datetime.strptime(data[0][0], "%Y-%m-%d").date() # データラングリング df0 = pd.DataFrame(data[1:]).dropna(how="all") df1 = df0[25].str.strip().str.split(r"\\n", 2, expand=True) se = df1.loc[df1[0].str.contains("携帯電話(その他基地局等"), 2] df2 = ( se.str.strip() .str.replace(r"\\n", "") .str.extractall("(.+?)\(([0-9,]+?)\)") .rename(columns={0: "市区町村名", 1: "開設局数"}) .reset_index(drop=True) ) df2["市区町村名"] = df2["市区町村名"].str.strip() df2["開設局数"] = df2["開設局数"].str.strip().str.replace(",", "").astype(int) # 都道府県を抽出 flag = df2["市区町村名"].str.endswith(("都", "道", "府", "県")) # 都道府県に移動 df2["都道府県名"] = df2["市区町村名"].where(flag).fillna(method="ffill") df2["市区町村名"] = df2["市区町村名"].mask(flag, "") # df2["更新日"] = update.isoformat() # 団体コードを付加 df3 = ( pd.merge(df2, df_code, on=["都道府県名", "市区町村名"], how="left") .reset_index(drop=True) .reindex(["団体コード", "都道府県名", "市区町村名", "開設局数"], axis=1) ) df3["団体コード"] = df3["団体コード"].astype("Int64") df3.sort_values("団体コード", inplace=True) df3.to_csv(f"{auth}.csv", index=False, encoding="utf_8_sig") # 都道府県 df_prefs = df3[flag].reset_index(drop=True) df_prefs.drop("市区町村名", axis=1, inplace=True) df_prefs.to_csv( pathlib.Path("data", f"{auth}_prefs.csv"), index=False, encoding="utf_8_sig" ) # 市区町村 df_cities = df3[~flag].reset_index(drop=True) df_cities.to_csv( pathlib.Path("data", f"{auth}_cities.csv"), index=False, encoding="utf_8_sig" ) return update if __name__ == "__main__": # ディレクトリ作成 pathlib.Path("data").mkdir(parents=True, exist_ok=True) updated = [] for m in musen: api["IT"] = m["value"] parm = urllib.parse.urlencode(api, encoding="shift-jis") update = fetch_api(parm, m["auth"]) updated.append([m["auth"], update]) time.sleep(3) df = pd.DataFrame(updated, columns=["area", "update"]) df.to_csv( pathlib.Path("data", "updated.csv"), index=False, encoding="utf_8_sig", )
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python機械学習】Kaggleのタイタニック予測問題に挑戦

はじめに 大学院で人工知能のモデルを研究している10個下の後輩に、Kaggleなるものを教えてもらったので、 早速、初めての機械学習、ならびにKaggleに挑戦してみました! Titanic - Machine Learning from Disaster ウェブマーケやってたときみたいにデータ取り込んで分析して〜っていうこの流れ めちゃめちゃワクワクする!! Kaggleとは Kaggleは企業や研究者がデータを投稿し、世界中の統計家やデータ分析家がその最適モデルを競い合う、予測モデリング及び分析手法関連プラットフォーム及びその運営会社である。 参照元:wikipedia Kaggleというのはプラットフォーム兼、運営会社なんですね。 いっぱい機械学習用のデータセットと課題が並んでいる。賞金モデルとか挑戦しがいがあるな〜 Titanic - Machine Learning from Disasterの概要 The sinking of the Titanic is one of the most infamous shipwrecks in history. On April 15, 1912, during her maiden voyage, the widely considered “unsinkable” RMS Titanic sank after colliding with an iceberg. Unfortunately, there weren’t enough lifeboats for everyone onboard, resulting in the death of 1502 out of 2224 passengers and crew. While there was some element of luck involved in surviving, it seems some groups of people were more likely to survive than others. In this challenge, we ask you to build a predictive model that answers the question: “what sorts of people were more likely to survive?” using passenger data (ie name, age, gender, socio-economic class, etc). 日本語翻訳(DeepL) タイタニック号の沈没は、歴史上最も悪名高い沈没事故のひとつです。 1912年4月15日、処女航海中の「不沈艦」と言われたRMSタイタニック号は、 氷山に衝突して沈没しました。不幸にも救命ボートの数が足りず、 乗客・乗員2224名のうち1502名が亡くなりました。 生き残るには運もありますが、あるグループは他のグループよりも生き残りやすいようです。 この課題では、 乗客のデータ(名前、年齢、性別、社会経済的階級など)を使って、 「どのような人たちが生き残りやすかったのか」 という問いに答える予測モデルを構築していただきます。 手順 データの読み込み 欠損値の補間 n/aなど平均値や0で埋める カテゴリー(例:男、女)は、番号にして、フラグやEnum扱いに置換 複数のモデルで検証し、一番良いモデルを選択 テストデータをそのモデルに当てはめて予測 結果のアウトプット コード ディレクトリ構成 titanic . ├── model.py # モデルを取得するファイル ├── result │   └── submission.csv # 結果出力ファイル(csv) ├── seed │   ├── titanic_test.csv # テストデータ(予測用) │   └── titanic_train.csv # 学習データ └── titanic.py # 実行ファイル titanic.py $ pip install scikit-learn # ver 0.24.2 titanic.py import numpy as np import pandas as pd import copy import model from sklearn.model_selection import cross_val_score # ハイパーパラメータの探査アルゴリズム...今回は断念 from sklearn.model_selection import GridSearchCV, RandomizedSearchCV """ ▽Data Set ・PassengerId:データにシーケンシャルでついている番号 ・Survived:生存(0 = No, 1 = Yes) 訓練用データにのみ存在 ・Pclass:チケットのクラス(1 = 1st, 2 = 2nd, 3 = 3rd) ・Name:名前 ・Sex:性別 ・Age:年齢 ・SibSp:タイタニック号に乗っていた兄弟と配偶者の数 ・Parch:タイタニック号に乗っていた両親と子どもの数 ・Ticket:チケット番号 ・Fare:旅客運賃 ・Cabin:船室番号 ・Embarked:乗船場(C = Cherbourg, Q = Queenstown, S = Southampton) """ # 使用するカラムを指定:チケットのクラス、性別、年齢、タイタニック号に乗っていた兄弟と配偶者の数、タイタニック号に乗っていた両親と子どもの数、旅客運賃、乗船場 predictors = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked"] def exec(): # データ読み込み train_raw = pd.read_csv("./seed/titanic_train.csv", dtype={"Age": np.float64}, ) test_raw = pd.read_csv("./seed/titanic_test.csv", dtype={"Age": np.float64}, ) # 欠損値の補間:(※一旦、ディープコピーして別物インスタンスに) train_corrected, test_corrected = correct_data(copy.deepcopy(train_raw), copy.deepcopy(test_raw)) results = [] names = [] models = model.get_models() for name, m in models: # 各モデルを使って交差検証を実施 # x:使用するデータセット(カラム指定)、y:予測する値、cv:○個あるデータブロックのうち、1個を検証用データ、その他を訓練データとして使う result = cross_val_score(m, train_corrected[predictors], train_corrected["Survived"], cv=3) names.append(name) results.append(result) # 予測率が一番高いモデルを取得 best_model = get_best_model(names, results) algo = None if best_model[0]['name'] == 'LogisticRegression': algo = model.LogisticRegression(max_iter=5000) elif best_model[0]['name'] == 'SVC': algo = model.SVC() elif best_model[0]['name'] == 'LinearSVC': algo = model.LinearSVC(dual=False) elif best_model[0]['name'] == 'KNeighbors': algo = model.KNeighborsClassifier() elif best_model[0]['name'] == 'DecisionTree': algo = model.DecisionTreeClassifier() elif best_model[0]['name'] == 'RandomForest': algo = model.RandomForestClassifier() else: algo = model.MLPClassifier(solver='lbfgs', random_state=0, max_iter=5000) # テストデータにおける予測とそのアウトプット output_result_as_csv(algo, train_corrected, test_corrected) def correct_data(train_raw, test_raw): # テストデータのmedianをとったほうが精度向上する。 train_raw.Age = train_raw.Age.fillna(test_raw.Age.median()) train_raw.Fare = train_raw.Fare.fillna(test_raw.Fare.median()) test_raw.Age = test_raw.Age.fillna(test_raw.Age.median()) test_raw.Fare = test_raw.Fare.fillna(test_raw.Fare.median()) # カテゴリーの補間 train_data = correct_common_data(train_raw) test_data = correct_common_data(test_raw) return train_data, test_data def correct_common_data(titanic_data): titanic_data.Sex = titanic_data.Sex.replace(['male', 'female'], [0, 1]) titanic_data.Embarked = titanic_data.Embarked.fillna("S") # これはバイアスかも titanic_data.Embarked = titanic_data.Embarked.replace(['C', 'S', 'Q'], [0, 1, 2]) return titanic_data def get_best_model(names, results): best_model = [] for i in range(len(names)): if i == 0: # 最初は比較対象の元となるので入れる best_model.append({'name':names[i], 'result':results[i].mean()}.copy()) elif best_model[0]['result'] < results[i].mean(): # 比較して、数値が良い方をセット best_model[0] = {'name':names[i], 'result':results[i].mean()} return best_model def output_result_as_csv(algo, train_corrected, test_corrected): # グリッドサーチ/ランダムサーチによるハイパーパラメータの最適化をしたかった...断念。 # ※ココでバグ発生する # param_grid = { # "max_depth": [3, None], #distribution # "n_estimators":[50,100,200,300,400,500], # "max_features": sp_randint(1, 11), # "min_samples_split": sp_randint(2, 11), # "min_samples_leaf": sp_randint(1, 11), # "bootstrap": [True, False], # "criterion": ["gini", "entropy"] # } # gsc = GridSearchCV(algo, parameters, cv=3) # rsc = RandomizedSearchCV(estimator=algo, param_distributions=param_grid, cv=5, n_iter=30) # scoring="accuracy" # 生存者データをモデルにフィット algo.fit(train_corrected[predictors], train_corrected["Survived"]) # gsc.fit # 欠損値補間済みのテストデータで予測 predictions = algo.predict(test_corrected[predictors]) # 必要カラムのみ取得 submission = pd.DataFrame({ "PassengerId": test_corrected["PassengerId"], "Survived": predictions }) # CSV出力 submission.to_csv('./result/submission.csv', index=False) # importされたときにtitanic.py自体が実行されないようにするおまじない if __name__ == "__main__": exec() model.py model.py from typing import List from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC, LinearSVC from sklearn.neighbors import KNeighborsClassifier from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.neural_network import MLPClassifier """ # 各モデルのハイパーパラメータを探すための引数を設定するオブジェクト...断念 # model_param_set_random = { # LinearRegression(): {}, # Lasso(): {}, # Ridge(): {}, # SVR(): { # "kernel": ["linear", "poly", "rbf", "sigmoid"], # "C": scipy.stats.uniform(0.00001, 1000) # }, # RandomForestRegressor(): { # "n_estimators": scipy.stats.randint(10, 300), # "max_depth": scipy.stats.randint(1, 20) # } # } ▽学習手法 ・ロジスティック回帰 ・サポートベクターマシン ・k-最近傍法 ・決定木 ・ランダムフォレスト ・ニューラルネットワーク """ def get_models()->list: models = [] models.append(("LogisticRegression", LogisticRegression(max_iter=5000))) models.append(("SVC", SVC())) models.append(("LinearSVC", LinearSVC(dual=False))) models.append(("KNeighbors", KNeighborsClassifier())) models.append(("DecisionTree", DecisionTreeClassifier())) models.append(("RandomForest", RandomForestClassifier())) models.append(("MLPClassifier", MLPClassifier(solver='lbfgs', random_state=0, max_iter=10000))) return models if __name__ == "__main__": get_models() 実行結果:36961/45420位 0.76315の正答率ですね! いいのか悪いのかも肌感でわからないこの、肌がゆさ。 まだまだモデルとか、どういったアルゴリズムで機械学習してその傾向性を導き出してるかなんて全然わかっておらず、悔しい気持ちでいっぱいですなあ。 また、モデルの精度向上なんかはもっと座学ベースで理論武装しないと感覚が掴めないですわ(`・ω・´) モデルざっくり説明 モデル名 説明 ロジスティック回帰 「∫」 みたいな感じの線で予測する統計的回帰モデル サポートベクターマシン 「∴/∴」 みたいな感じで別グループにもっとも近い点を探して、その点と点を結ぶ距離が。それぞれ最大になるように分ける k-最近傍法 「∴◎∴」 みたいな感じで学習データをベクトル空間上にプロットしておき、未知のデータが得られたら、そこから距離が近い順に任意のK個を取得し、多数決でデータが属するクラスを推定する 決定木 「众」 みたいな感じで、分類木と回帰木を組み合わせたもので、ツリー(樹形図)によってデータを分析する手法 ランダムフォレスト 「众众众」 みたいな感じで決定木をたくさん集めたもの ニューラルネットワーク 「井井井」 みたいな感じで人工ニューロン(ノード)が、学習によってシナプスの結合強度を変化させ、問題解決能力を持つようなモデル全般 ハマったポイント 欠損値の補間が「補完」ではないことを初めて知った ここの調整で結果が結構変わってきそうな予感 自分はここの調整がまだまだできていない。 モデルの選択はもうsklearnライブラリに任せっきりで、自分で深く理解して選んだ感がないので、不安になる。 テストデータを使って補間することで精度が上がることを初めて知った。 補間の仕方で精度を調整できるので熟練の技が必要だ ハイパーパラメータを使うことで、モデルの精度を向上できるみたいだが、計算リソースを食ったり時間がかかるので、設定が巧妙じゃないとなかなか難しい。 もう少しお勉強が必要ですorz 感想とまとめ ウェブマーケやっていたときに、広告配信インプレッション数を溜め込んで、傾向性を把握するために、タブローとかでグラフで可視化したりしていたので、案外とっつき易かったです。 機械学習、なるほど〜って感じ?✨ 結局やっていることって データを元に、傾向を把握して、その傾向にテストデータを当てはめたときに、どうなるかっていう、 既存のデータで作られた「線」の延長線上にテストデータを散りばめて どっちですか!!?って予測してくもの なんだな〜って。 楽しい学問かつプログラミングでした!✨ もっと知識を深めて、データサイエンティストになりたい(☝︎ ՞ਊ ՞)☝︎ 以上、ありがとうございました! 参考記事 参考にさせていただきました! 概念をつかむのにめっちゃわかりやすかったです?✨
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python画像読み込みエラー

import cv2 print(cv2.version) image = cv2.imread("./test.jpg") image2 = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) cv2.namedWindow('image') while True: cv2.imshow('image', image) if cv2.waitKey(1) & 0xFF == ord('q'): break cv2.destroyAllWindows() 実行後 error: OpenCV(4.5.1) /private/var/folders/nz/vv4_9tw56nv9k3tkvyszvwg80000gn/T/pip-req-build-yvyj7qlp/opencv/modules/imgproc/src/color.cpp:182: error: (-215:Assertion failed) !_src.empty() in function 'cvtColor' ここを参考に https://qiita.com/Nw3965/items/5ef4bc82dd9c28e36a19 3行目を絶対パスに変更 image = cv2.imread("./test.jpg") ↓ image = cv2.imread("/Users/○○○/Desktop/test.jpg")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ADX2のツールからobbを作る

はじめに Oculusのアプリで、極端に大きなapkだと分割してストアに上げないといけないのですが、 ADX2のデータは、Unityの標準のobb(APKの拡張ファイル)ではストリーム再生ができないので、 Android Studioにあるjobbなるツールでまとめる必要がある。 このobbを作成し、さらにOculusの実機へpushするのが手間なので、スクリプト1回実行して済ませるようなものを作ってみました。 この記事の対象 ADX2でjobbでobbを作りたい人 ADX2のスクリプトで外部プロセスを呼びたい人 環境 Craft 3.45.00 jobb jobbは以下 これを使えばよいのですが、バグがあるっぽく別途 のfat32を使う必要がある。 adb adbもAndroid Studioに入っている。 Android studio JDK などインストールや、 adb,jobbのパスなど環境変数の設定などが必要かも。 スクリプト subprocessで2つ実行しています。 失敗したときの処理があまりないので、変な時は手動で中断してください。 パスやデバイスIDなども適宜書き換えてください。 MDS_MakeObb.py # --Description:[tatmos][Build]adx2フォルダをObbファイルにまとめる import sys import os import cri.atomcraft.debug as acdebug import subprocess import cri.atomcraft.view as acview # まとめるフォルダ srcPath = "C:/MyDearest/github/MDSoundPreview/Assets/StreamingAssets/adx2/" # 出力フォルダ outPath = "C:/MyDearest/" # 出力するObbファイル名 outputFileName = "ADX2.obb" # アプリのビルドバージョン appBuildVersion = 1 # アプリのPackageName appPackageName = "com.MyDearest.MDSoundPreview" # 転送先のデバイスID : adb devicesなどで調べる deviceId = "XXXXXXXXXXXXXX" if not os.path.exists(srcPath): acdebug.log("srcPath がありません。") sys.exit() # jobb cmd1 = "jobb.bat" cmd2 = "-d \"{0}\"".format(srcPath); cmd3 = "-o \"{0}\"".format(outPath + outputFileName); cmd4 = "-pn \"{0}\"".format(appPackageName); cmd5 = "-pv {0}".format(appBuildVersion); acdebug.log([cmd1 + " " + cmd2 + " " + cmd3 + " " + cmd4 + " " + cmd5]) message = subprocess.check_output(cmd1 + " " + cmd2 + " " + cmd3 + " " + cmd4 + " " + cmd5) acdebug.log(message.decode().strip()) message = "続いて転送します" button_name_list = ["続ける", "キャンセル"] result = acview.show_dialog(message, button_name_list) if result [ "data" ][ "button_name" ] == "キャンセル" or result [ "data" ][ "button_index" ] == "-1": sys.exit() # push cmd1 = "adb" cmd2 = "-s {0}".format(deviceId); cmd3 = "push {0}".format(outPath + outputFileName); cmd4 = "/storage/emulated/0/Android/obb/{0}/{1}".format(appPackageName,outputFileName); acdebug.log([cmd1 + " " + cmd2 + " " + cmd3 + " " + cmd4]) message = subprocess.check_output(cmd1 + " " + cmd2 + " " + cmd3 + " " + cmd4) message2 = message.decode().strip().split("\r\n") for m in message2: if not str(m).startswith("["): acdebug.log(m) ログ抑制 check_outputで帰ってくるメッセージに進捗的なものが含まれていて、 これをログ出力するとADX2が重くなるので、分割しつつ、不要な進捗は表示しないようにしています。 check_output message2 = message.decode().strip().split("\r\n") for m in message2: if not str(m).startswith("["): acdebug.log(m) おわりに adbコマンドも叩けるので、少し応用すれば、 サウンドデータ差し替えて、転送して、実行して、実機で確認 とかがなるべく手間なくできる環境が作れるかと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gitレポジトリの内部のデータ構造をGraphvizで描画してみた 第2回 ブランチとマージ

本稿は「 Gitレポジトリの内部のデータ構造をGraphvizで描画してみた 第1回 commitとtreeとblob 」の続編です。 解決すべき問題 一年前のこと、Subversionを長く使ってきた開発チームにGitを教えようとした。わたしはGitのブランチをちゃんと説明したかった。というのもSubversionにもブランチという用語があるが、GitのブランチはSubversionとは似て非なるものだからだ。"Git"と"Subversion"と"ブランチ"をキーにネットを検索すると解説記事がたくさんみつかった。だがわたしが同僚諸君に説明するのに使えるようなひと目で理解できる記事が見当たらなかった。 いま自分の手元にあるプロジェクトの.gitディレクトリのなかにあるGitレポジトリの実物を読み出して図にしてくれる、そういうツールがほしい。 解決方法 Pythonでツール kazurayam/visualize_git_repository.py を開発した。これを使えばいま自分の手元にあるプロジェクトの .git ディレクトリのなかにあるオブジェクト群の実物を読み出し、Graphvizでグラフを生成してPNG画像ファイルを出力することができる。 説明 デモ用にディレクトリを作りGitレポジトリを作ろう。masterブランチとdevelopブランチでファイルを追加・変更などして、developをmasterにマージしよう。Gitレポジトリのなかがどんなふうに形を変化させていくか、図を示しつつ、説明しよう。 ステップ1 最初のコミット 新しいGitレポジトリを作ろう。いくつかファイルを作ってコミットしよう。 作業用ディレクトリを作った git initした ファイルを3つ作った。README.md、.gitignore、src/greeting.plを。 git add .した git commit -m "initial commit'した この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 master develop まだ無い このグラフから次のことが読みとれる。 git initするとmasterという参照名が自動的に作られる。 Gitにおいてブランチとはひとつのcommitオブジェクトを指す別名にすぎない。上記の図で参照名 master は initial commit というコメントを含むひとつのcommitオブジェクトを指す別名にすぎない。 Gitには HEAD という名前の参照名が組み込みで定義されている。HEADもやはりひとつのcommitオブジェクトを指す。上記の図でHEADは参照名masterを指しているが、masterがcommitオブジェクトを指すので、けっきょくHEADもmasterを介して間接的にひとつのcommitオブジェクトを指すと解釈することができる。 HEADは特殊な参照名だ。さまざまのgit xxxコマンドを実行した副作用としてHEADが指すモノが切り替わる。 参照名HEADが参照名masterを指しているという状態を「カレント・ブランチがmasterである」と表現することもある。この言い方のほうが日本語としてすなおなので多用するだろう。以下で「カレント・ブランチが...」という文がでてきたら、参照名HEADから伸びた矢印 → が何を指しているかを思い浮かべてほしい。 ステップ2 developブランチを作る 新しいブランチ develop を追加しよう。 カレント・ブランチがmasterである状態で git branch developコマンドを実行した この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 master develop 前と同じ このグラフから次のことが読みとれる。 新しい参照名 develop が作られた。 参照名 develop はinitial commitというコメントを含む commitオブジェクト すなわち master が指していたのと同じcommitオブジェクトを指している。 参照名HEADが参照名developを指すかたちに変更された。別の言い方をすると カレント・ブランチがmasterからdevelopに切り替えられた。 カレント・ブランチがdevelopに切り替えられた後でも参照名masterはGitレポジトリのなかに存続している。上の図ではmasterの図を省略したがそれは図をシンプルにして見やすくするためであって、消えてなくなったわけではない。 ステップ3 developでコミットする developブランチで新しいファイルを追加してコミットしよう。 ワーキングツリーで新しいファイル doc/TODO.txt を追加した カレント・ブランチがdevelopであることをたしかめて git add .を実行した git commit -m "add doc/TXT.txt"を実行した この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 master develop 前と同じ このグラフから次のことが読みとれる。 新しいcommitオブジェクトができた。新しいcommitオブジェクトが親commitにリンクする形になり、commitオブジェクトの鎖がひとつ伸びた。新しいcommitオブジェクトはルートディレクトリ / を指すtreeオブジェクトにリンクしている。treeのなかには doc/TODO.txt ファイルのblobオブジェクトがある。つまり追加したファイルがちゃんと認識されている。 参照名developすなわちdevelopブランチがどのcommitオブジェクトを指しているか?を見ると自動的に更新されたのがわかる。ブランチ名はcommitオブジェクトの鎖の端っこ(ゴーヤの蔓の成長点のようなところ)を指すようにgitによって自動的に更新されるのだ。 ステップ4 masterブランチに戻る ファイル README.md を修正してコミットする前に、masterブランチに戻ろう。 git checkout master を実行した この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 master develop 前と同じ このグラフから次のことが読みとれる。 参照名 HEAD が参照名 master を指すように更新された。すなわちカレント・ブランチがmasterに切り替わった。 masterブランチの中身は1回目のcommitをした時のままで変わっていない。 カレント・ブランチがmasterに切り替えられた後でも参照名developはGitレポジトリのなかに存続している。上の図ではdevelopの図を省略したがそれは図をシンプルにして見やすくするためであって、消えてなくなったわけではない。 ステップ5 masterでコミットする masterブランチで既存のREADME.mdファイルを変更してコミットしよう。 git branch --show-currentとやってカレント・ブランチがmasterであることを確かめた echo "#Read me carefully" > README.mdとやってファイルを更新した git add .した git commit -m "modified README.md"とやった この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 master develop 前と同じ このグラフから次のことが読みとれる。 新しいcommitオブジェクトができた。新しいcommitオブジェクトが指すtreeのなかにあるREADME.mdファイルのblobをみるとhash値が変わっている。つまり変更されたREADME.mdファイルのために新しいblobオブジェクトがひとつ生成されて、そのhash値をcommitが参照している。これこそが「変更後のREADME.mdファイルがレポジトリにコミットされた」という表現の意味するところだ。 参照名 master が新しいcommitオブジェクトを指すように更新された。 ステップ6 developをmasterにマージする developブランチをmasterブランチにマージしよう。すなわちdevelopブランチで行ったファイルの追加・変更・削除をmasterブランチにとり込もう。 git branch --show-currentとやってカレント・ブランチがmasterであることを確かめた git merge developとやった この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 master develop 前と同じ このグラフから次のことが読みとれる。 マージする前にmasterブランチにはcommitオブジェクトが2個あった。マージした後にmasterブランチにcommitオブジェクトが4個ある。2個増えた。 増えた2個のcommitオブジェクトうちひとつはdevelopブランチで作られたものだ。add doc/TODO.txtというコミットメッセージをもっている。developブランチだけで見えていたcommitオブジェクトがgit merge developコマンドによってmasterブランチでも見えるようになった。 増えた2個のcommitオブジェクトのうち残るひとつは新しくmasterブランチに追加されたものだ。このcommitオブジェクトは親(parent)コミットを2つ持っている点が特徴的だ。これをマージコミットともいう。 マージコミットの親コミット2つとはカレント・ブランチの先端にあったcommitオブジェクトと、マージされたブランチの先端にあったcommitオブジェクトである。マージコミットによってコミットの履歴2本が合流する。マージコミットより古い履歴は2本のまま残されて、マージコミットからあとは1本の履歴が始まる。 参照名 master はマージコミットを指すように自動的に更新された。 マージコミットは普通のcommitオブジェクトと同様にルートディレクトリ / に対応するtreeオブジェクトへの参照をもっている。そのtreeオブジェクトを起点としてすべてのファイルのblobオブジェクトを参照することができる。それらblobはmasterブランチとdevelopブランチにおいてこれまでに追加・変更・削除されたファイルの最新状態が反映されている。 上の図ではマージコミットが指しているtreeオブジェクトだけを描き、時系列的に古いcommitオブジェクトが指しているtreeオブジェクトを省略した。これは図を簡素化するためだ。treeオブジェクトがGitレポジトリから消えて無くなったわけではない。 ツールについて 本稿で示したPNG画像は自作のツール visualize_git_repository で描画した。このツールはPython言語で開発した。ソースコードは下記のGitHubレポジトリにある。 https://github.com/kazurayam/visualizing-git-repository このツールは下記2つのライブラリを利用している。 pytest python graphviz PNG画像を生成するにはコマンドラインで下記の操作をする。 $ cd $visualize_git_repository $ pytest -s kazurayam/visualize_git_repository.py::test_2_branch_and_mearge 上記の例を作るのにどういうgitコマンドを実行したのかを知りたいならプログラムのソースコードを読み解いてください。下記を入り口として解読してください。 kazurayam/visualize_git_repository.py まとめ 一年前の同僚諸君に本稿を読んでもらいたいなあ。これならGitのブランチとマージが理解できるだろうと思うのだが、どうだろうか? author: kazurayam date: June, 2021
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pagenationの高速化に取り組んだ

クエリの最適化を学習しようと思い簡単なwikiサイトの作成に取り組み10万件のデータを入れて色々試してみました。 Django Debug Toolbarを見てみると ListViewでそのデータの件数を数えている部分が時間がかかっているようなので なんらかの方法で短く出来ないか模索してみました。 そもそもカテゴリー分けしてカテゴリー一覧を表示させたほうがいいのかもしれませんが あくまで学習として取り組んでみました。 こんなmodelがある前提で書いていきます。 本当はもっとフィールドや外部キーがあるのですが内容と直接関係ないので省略してます。 models.py class Wiki(models.Model): title = models.CharField(max_length=100, verbose_name="タイトル") created = models.DateTimeField(auto_now=True, verbose_name="作成日時") updated = models.DateTimeField(auto_now_add=True, verbose_name="更新日時") def __str__(self): return self.title やったこと wikiの件数を数字で保存するmodelを作って 既存のPagenatorを継承したクラスを作って 単純にその数字を返すようにしました。 wikiを保存する毎に+1する感じです。 もともとデータを数える部分は40ms程かかっていたのですが 結果としては1.3ms程になりました。 modelの作成 models.py class Count(models.Model): number = models.PositiveIntegerField(verbose_name="個数") created = models.DateTimeField(auto_now=True, verbose_name="作成日時") updated = models.DateTimeField(auto_now_add=True, verbose_name="更新日時") def __str__(self): return str(self.number) こんなmodelを作ります。 Pagenatorの作成 ├── app │   ├── __init__.py │   ├── asgi.py │   ├── settings.py │   ├── urls.py │   └── wsgi.py ├── manage.py ├── requirements.txt ├── static ├── templates │   ├── base.html │   └── wiki │   └── index.html └── wiki ├── __init__.py ├── admin.py ├── apps.py ├── models.py ├── pagenators.py #ここ ├── tests.py ├── urls.py └── views.py pagenators.pyを作成します。 上記のような構成です。 ファイルを編集していきます。 paginators.py from django.core.paginator import Paginator from django.utils.functional import cached_property from .models import Count class WikiPaginator(Paginator): @cached_property def count(self): count = Count.objects.values_list('number', flat=True)[0] return count Djangoのソースコードを見てPagenatorクラスのcount関数の返す値を 先ほど作ったmodelの値を返すようにしました。 @cached_property親クラスのcount関数でも使われていたのでそれにならってつけています。 まだよくわかってないのでそれについてはもうちょっと学習してみようと思います。 view views.py from django.views.generic import ListView from .pagenators import WikiPaginator class IndexView(LoginRequiredMixin, ListView): template_name = 'wiki/index.html' model = Wiki context_object_name = "wikis" paginate_by = 10 paginator_class = WikiPaginator paginator_classを指定出来るので先ほど作成したWikiPaginatorを指定します。 あとはWikiの作成時に例えばform_validを使ってCountのnumberに+1するようにすればオッケーです。 最後に もっといい方法やご指摘があったら教えていただけたら幸いです。 参考URL https://github.com/django/django/blob/main/django/core/paginator.py https://github.com/django/django/blob/main/django/views/generic/list.py
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoのpagenationの高速化に取り組んだ

クエリの最適化を学習しようと思い簡単なwikiサイトの作成に取り組み10万件のデータを入れて色々試してみました。 Django Debug Toolbarを見てみると ListViewでそのデータの件数を数えている部分が時間がかかっているようなので なんらかの方法で短く出来ないか模索してみました。 そもそもカテゴリー分けしてカテゴリー一覧を表示させたほうがいいのかもしれませんが あくまで学習として取り組んでみました。 こんなmodelがある前提で書いていきます。 本当はもっとフィールドや外部キーがあるのですが内容と直接関係ないので省略してます。 models.py class Wiki(models.Model): title = models.CharField(max_length=100, verbose_name="タイトル") created = models.DateTimeField(auto_now=True, verbose_name="作成日時") updated = models.DateTimeField(auto_now_add=True, verbose_name="更新日時") def __str__(self): return self.title やったこと wikiの件数を数字で保存するmodelを作って 既存のPagenatorを継承したクラスを作って 単純にその数字を返すようにしました。 wikiを保存する毎に+1する感じです。 もともとデータを数える部分は40ms程かかっていたのですが 結果としては1.3ms程になりました。 modelの作成 models.py class Count(models.Model): number = models.PositiveIntegerField(verbose_name="個数") created = models.DateTimeField(auto_now=True, verbose_name="作成日時") updated = models.DateTimeField(auto_now_add=True, verbose_name="更新日時") def __str__(self): return str(self.number) こんなmodelを作ります。 Pagenatorの作成 ├── app │   ├── __init__.py │   ├── asgi.py │   ├── settings.py │   ├── urls.py │   └── wsgi.py ├── manage.py ├── requirements.txt ├── static ├── templates │   ├── base.html │   └── wiki │   └── index.html └── wiki ├── __init__.py ├── admin.py ├── apps.py ├── models.py ├── pagenators.py #ここ ├── tests.py ├── urls.py └── views.py pagenators.pyを作成します。 上記のような構成です。 ファイルを編集していきます。 paginators.py from django.core.paginator import Paginator from django.utils.functional import cached_property from .models import Count class WikiPaginator(Paginator): @cached_property def count(self): count = Count.objects.values_list('number', flat=True)[0] return count Djangoのソースコードを見てPagenatorクラスのcount関数の返す値を 先ほど作ったmodelの値を返すようにしました。 @cached_property親クラスのcount関数でも使われていたのでそれにならってつけています。 まだよくわかってないのでそれについてはもうちょっと学習してみようと思います。 view views.py from django.views.generic import ListView from .pagenators import WikiPaginator class IndexView(LoginRequiredMixin, ListView): template_name = 'wiki/index.html' model = Wiki context_object_name = "wikis" paginate_by = 10 paginator_class = WikiPaginator paginator_classを指定出来るので先ほど作成したWikiPaginatorを指定します。 あとはWikiの作成時に例えばform_validを使ってCountのnumberに+1するようにすればオッケーです。 最後に もっといい方法やご指摘があったら教えていただけたら幸いです。 参考URL https://github.com/django/django/blob/main/django/core/paginator.py https://github.com/django/django/blob/main/django/views/generic/list.py
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python勉強記録 #4 Pandasの基礎

Pandasとは 機械学習のプログラミングでよく用いられるライプラリで、データ解析を容易にする機能を持っています。 データフレーム(DataFrame)などの独自のデータ構造によって様々な処理が可能になります。 モジュール、パッケージ、ライブラリについて モジュール…関数を格納する部品のようなもの。「.py」というテキストファイル。例えるなら基盤。 パッケージ…モジュールの集まり。例えるなら炊飯器。 ライブラリ…パッケージをいくつかまとめてインストールできるようにしたもの。例えるならキッチン。 Pandas基本操作 Pandasのインストール import pandas as pdと最初に入力しておけば、そのファイル内でpdと入力すればPandasのライブラリの中にある機能を使うことができるようになります。 データの読み込み csvファイルを読み込む時はこのようにします。 # CSVファイルの読み込み pd.read_csv('sample.csv') # 読み込んだCSVファイルをdfという変数に代入 df = pd.read_csv('sample.csv') このような形で、読み込まれた時、データの型は、 type(df) """ 出力結果 : pandas.core.frame.DataFrame """ となります。これはデータフレームと呼ばれる型であることを意味しています。 読み込んだデータを確認する方法として、 df.shape データの形を表示(行数、列数を確認) df.head(3) データフレームの上から3行を表示 df.tail(3) データフレームの下から3行を表示 などがあります。 データの切り分け ilocメソッドを使います。 # 1行目、1列目を切り出してtに代入 t = df.iloc[0, 0] # 全ての行、1列目を切り出してtに代入 t = df.iloc[:, 0] # 全ての行、2列目以降を切り出してtに代入 t = df.iloc[:, 1:] # 列名(ラベル)を直接指定して切り出してtに代入 t = df['A'] t = df.loc[:, 'A'] # 複数の列名(ラベル)を指定して切り出してtに代入 t = df.loc[:, ['A', 'B']] ilocとlocの違い loc 行ラベル、 列ラベルを指定して切り出し iloc 行の番号(0 ~ )、列の番号(0 ~ )を指定して切り出し データの並び替え sort_valueメソッドを使います。 # Aの列を昇順で並び替え df.sort_values('A') # Aの列を降順で並び替え df.sort_values('A', ascending=False) 列の追加・削除 dropメソッドを使います。 #aという変数に3行1列のデータフレームを代入 a = pd.DataFrame([[1],[2],[3]]) # Aというラベル名の列にaを追加 df['A'] = a # dfからAというラベル名の列の削除 df.drop(labels='A',axis=1) 追加の際は、追加する列の行数が追加するデータフレームの行数と一致していないとエラーが起こります。 axis=1は列方向に削除 axis=0は行方向に削除 するための引数です。 データの絞り込み A列の値が3のデータだけ表示させる時は、 # dfというデータフレームで、A列の値が3の行を表示 df[df['A'] == 3].head() となります。 複数条件をつける時は、 # dfというデータフレームで、A列の値が3、かつBの値が4の行を表示 df[(df['A'] == 3) & (df['B'] == 4)].head() # dfというデータフレームで、A列の値が3、またはBの値が4の行を表示 df[(df['A'] == 3) | (df['B'] == 4)].head() となります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python ペル方程式の最小解を求める

ペル方程式とは $$x^2 - D y^2 = 1$$ はペル方程式と呼ばれており, $\sqrt{D}$ の連分数展開によって最小解を求めることができる。 $$\sqrt{D} = a_0 + \cfrac{1} {a_1 + \cfrac{1} {a_2 + \cfrac{1}{\ddots}} }$$ こちらが非常にわかりやすいです。 こちらの記事はわかりやすく解説されており、連分数を求めるアプリも開発されています。 実装 こちらに置きました。 import math from decimal import ( Decimal, getcontext ) def is_square(n): is_square = False for i in range(2, int(math.sqrt(n)) + 1): if n == i ** 2: is_square = True break return is_square def get_sqrt(D): getcontext().prec = 100 x = int(D) for i in range(20): x = Decimal(str(x - (x ** 2 - D) / (2 * x))) return x def get_continued_fraction(D): continued_fraction = [] if not is_square(D): y = get_sqrt(D) - int(get_sqrt(D)) continued_fraction.append(int(1 / y)) z = 1 / y - int(1 / y) while True: if abs(y - z) < 1e-5: break else: continued_fraction.append(int(1 / z)) z = 1 / z - int(1 / z) return continued_fraction def get_minimal_solution(D): continued_fraction = get_continued_fraction(D) length = len(continued_fraction) num = length % 2 + 1 continued_fraction = continued_fraction * num an2, bn2 = 0, 0 an1, bn1 = 0, 0 an, bn = 1, 0 for n in range (1, num * length + 1): if n == 1: an1, bn1 = int(math.sqrt(D)), 1 else: an2, bn2 = (an + continued_fraction[n - 2] * an1, bn + continued_fraction[n - 2] * bn1) an, bn = an1, bn1 an1, bn1 = an2, bn2 print_continued_fraction = continued_fraction[:int(len(continued_fraction) / num)] print_continued_fraction.insert(0, int(get_sqrt(D))) print('連分数展開: {}'.format(print_continued_fraction)) return an2, bn2 if __name__ == '__main__': d = 2 x, y = get_minimal_solution(d) print('最小解\n x: {}\n y: {}'.format(x, y)) 出力例 $D=2$ 連分数展開: [1, 2] 最小解 x: 3 y: 2 処理時間: 0.0020055770874023438 $D=7$ 連分数展開: [2, 1, 1, 1, 4] 最小解 x: 8 y: 3 処理時間: 0.0009946823120117188 $D=14$ 連分数展開: [3, 1, 2, 1, 6] 最小解 x: 15 y: 4 処理時間: 0.0010349750518798828 $D=61$ 連分数展開: [7, 1, 4, 3, 1, 2, 2, 1, 3, 4, 1, 14] 最小解 x: 1766319049 y: 226153980 処理時間: 0.0010004043579101562 $D=751$ 連分数展開: [27, 2, 2, 8, 1, 2, 1, 3, 5, 1, 4, 1, 1, 1, 3, 1, 1, 3, 10, 1, 2, 7, 2, 17, 1, 4, 27, 4, 1, 17, 2, 7, 2, 1, 10, 3, 1, 1, 3, 1, 1, 1, 4, 1, 5, 3, 1, 2, 1, 8, 2, 2, 54] 最小解 x: 7293318466794882424418960 y: 266136970677206024456793 処理時間: 0.0009648799896240234 $D=991$ 連分数展開: [31, 2, 12, 10, 2, 2, 2, 1, 1, 2, 6, 1, 1, 1, 1, 3, 1, 8, 4, 1, 2, 1, 2, 3, 1, 4, 1, 20, 6, 4, 31, 4, 6, 20, 1, 4, 1, 3, 2, 1, 2, 1, 4, 8, 1, 3, 1, 1, 1, 1, 6, 2, 1, 1, 2, 2, 2, 10, 12, 2, 62] 最小解 x: 379516400906811930638014896080 y: 12055735790331359447442538767 処理時間: 0.002000093460083008 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

変数に複数行の文字列を代入

自分用の備忘録です。 トリプルクウォート""", '''を使う シングルクウォート'またはダブルクウォート"を3つ連続させて、文字列を囲う。 ipython In [12]: s = ''' ...: a b c d e ...: f g h i j ...: k l m n o ...: ''' 改行も反映される。 クリップボードからサクッと貼り付けたい場合に便利。 ipython In [13]: s Out[13]: '\na b c d e\nf g h i j\nk l m n o\n' In [14]: print(s) a b c d e f g h i j k l m n o 注意点としては、プログラムが見にくくなるため推奨はされないらしい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RustでPythonを使う

ソースコード python 1秒毎に文字を標準出力するスクリプト。 import time for i in range(5): print(i) time.sleep(1) print("end") Rust thread::sleepを使って0.5秒毎にPythonの出力結果をstdout.read_line(&mut line)でlineに書き込み、println!で表示させる。 use std::{env, thread}; use std::io::{BufReader, BufRead}; use std::process::{Stdio, Command}; use std::time::Duration; /// 実行ファイルの場所を取得する pub fn get_exe_dir() -> String { match env::current_exe() { Ok(p) => { let mut path = p.clone(); path.pop(); match path.into_os_string().into_string() { Ok(p2) => { return p2; } Err(_) => "./".to_string() } } Err(_) => { "./".to_string() } } } fn main() { let mut child = Command::new("python") .args(&["-u".to_string(), get_exe_dir() + "/../../test.py"]) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .unwrap(); let mut stdout = BufReader::new(child.stdout.as_mut().unwrap()); let mut line = String::new(); for _ in 0..10 { line.clear(); match stdout.read_line(&mut line) { Ok(_) => println!("{}", line), Err(e) => println!("{}", e) }; thread::sleep(Duration::from_millis(500)); }; } 解説 Python -u Rustに限らず、外からPythonを実行させるときは、-u引数を付けて実行させないと、結果がバッファにたまり、スレッドがブロックされ、完了後に再び動作するという状態になる。なかなか気が付きにくいハマりどころ。 Stdio::piped() .stdout(Stdio::piped())をつけることで、Commandの出力をBufReader::new(child.stdout.as_mut().unwrap());を使って結果を取得する。 Command::new("python") Command::new("python")でPythonを呼び出しているが、例えば仮想環境やEmbeddedを使う場合は、絶対パスを指定すれば良い。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RustでPythonをコマンド実行

メインスレッドを止めず、Pythonからの出力結果は変数で受け取りたくて調べてみた。 単純なソースコード python 1秒毎に文字を標準出力するスクリプト。 import time for i in range(5): print(i) time.sleep(1) print("end") Rust thread::sleepを使って0.5秒毎にPythonの出力結果をstdout.read_line(&mut line)でlineに書き込み、println!で表示させる。 追記:このままではstdout.read_lineでPythonからの出力を待つ状態になるので、下部の「制御も別スレッドに分ける」に記載しているコードのように、メインスレッドとは完全に分けるか、非同期処理で動作させる方が良いかもしれない。 use std::{env, thread}; use std::io::{BufReader, BufRead}; use std::process::{Stdio, Command}; use std::time::Duration; /// 実行ファイルの場所を取得する pub fn get_exe_dir() -> String { match env::current_exe() { Ok(p) => { let mut path = p.clone(); path.pop(); match path.into_os_string().into_string() { Ok(p2) => { return p2; } Err(_) => "./".to_string() } } Err(_) => { "./".to_string() } } } fn main() { let mut child = Command::new("python") .args(&["-u".to_string(), get_exe_dir() + "/../../test.py"]) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .unwrap(); let mut stdout = BufReader::new(child.stdout.as_mut().unwrap()); let mut line = String::new(); for _ in 0..10 { line.clear(); match stdout.read_line(&mut line) { Ok(_) => println!("{}", line), Err(e) => println!("{}", e) }; thread::sleep(Duration::from_millis(500)); }; } 解説 Python -u Rustに限らず、外からPythonを実行させるときは、-u引数を付けて実行させないと、結果がバッファにたまり、スレッドがブロックされ、完了後に再び動作するという状態になる。なかなか気が付きにくいハマりどころ。 Stdio::piped() .stdout(Stdio::piped())をつけることで、Commandの出力をBufReader::new(child.stdout.as_mut().unwrap());を使って結果を取得する。 Command::new("python") Command::new("python")でPythonを呼び出しているが、仮想環境やEmbeddedを使う場合は、"python"の部分に絶対パスを指定すれば良い。 例えばAnacondaやminicondaを使っている場合は、コマンドプロンプト等でconda info -eを実行すれば絶対パスを取得出来る。 制御も別スレッドに分ける Pythonの監視も別スレッドにして結果をチャンネルを使って受け取ったりすれば、使い勝手が良くなるかもしれない。 use std::{env, thread}; use std::io::{BufReader, BufRead}; use std::process::{Stdio, Command}; use std::time::Duration; use std::sync::mpsc::{Sender, Receiver, channel}; use std::any::Any; /// 実行ファイルの場所を取得する pub fn get_exe_dir() -> String { match env::current_exe() { Ok(p) => { let mut path = p.clone(); path.pop(); match path.into_os_string().into_string() { Ok(p2) => { return p2; } Err(_) => "./".to_string() } } Err(_) => { "./".to_string() } } } fn main() { // メッセージ用のチャンネル let (message_tx, message_rx): (Sender<String>, Receiver<String>) = channel(); // Python停止命令用のチャンネル let (is_end_tx, is_end_rx): (Sender<bool>, Receiver<bool>) = channel(); // // ここから別スレッド // thread::spawn(move || { let mut child = Command::new("python") .args(&["-u".to_string(), get_exe_dir() + "/../../test.py"])// 実行ファイルから2つ上のtest.pyを実行する。 .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .unwrap(); // この変数に結果が入る let mut line = String::new(); // ループさせる。返り値はPythonが終了しているかどうか。 let is_terminate = 'l: loop { // childが終了命令より前に終了しているかどうかを確認。 match child.try_wait() { Ok(res) => { match res { Some(res2) => { // 終了したかどうか if res2.success() { message_tx.clone().send(format!("exit code {}", res2.code().unwrap_or(-1))); break 'l true; }else{ // resに値が入っていても、success()がtrueとは限らない } } None => {} } } Err(e) => { println!("{}", e); break 'l false; } } let mut stdout = BufReader::new(child.stdout.as_mut().unwrap()); // 終了命令が無いか調べる match is_end_rx.try_recv() { Ok(res) => { // あればループを抜ける。 if res { break 'l false; }; } Err(_) => {} } line.clear(); // Pythonから出力があるまでこのスレッドは停止。 match stdout.read_line(&mut line) { // メッセージをメインスレッドに送る。 Ok(_) => { message_tx.clone().send(line.to_string()) } Err(e) => { println!("{}", e); // メッセージが読み込まれない場合は終了する。 break 'l false; } }; }; if !is_terminate { // Python終了 match child.kill() { Ok(()) => println!("-> python end"), Err(e) => println!("{}", e) }; }; }); // // ここからはメインスレッド // // n秒後に停止するまでメッセージを受け取る for _ in 0..10 { // try_recvで受け取るようにすると、スレッドが停止しない。 match message_rx.try_recv() { Ok(res) => { // Pythonの出力を受け取る。 println!("rust receiver {}", res); } Err(_) => { // 開始のタイミングがズレて、開始前にここに到達することがあるため、ここでbreakはしない。 } } thread::sleep(Duration::from_millis(1000)); } // 別スレッドへ終了命令を送る is_end_tx.send(true); thread::sleep(Duration::from_millis(3000)); println!("program end"); }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

二項分布と正規分布が一致する場合

はじめに 二項分布と正規分布が一致する条件を調べる。 二項分布 P(X=k)={ }_{n} \mathrm{C}_{k} p^{k}(1-p)^{n-k} \quad(k=0,1,2, \cdots, n) についてp=1/2、n=100とすると P(X=k)= \frac{{ }_{100} \mathrm{C}_{k}}{2^{100}} \quad(k=0,1,2, \cdots, n) 正規分布 p(x)=\frac{1}{\sqrt{2 \pi \sigma^{2}}} e^{-\frac{(x-\mu)^{2}}{2 \sigma^{2}}} についてμ=50、σ=5とすると p(x)=\frac{1}{5\sqrt{2 \pi }} e^{-\frac{(x-50)^{2}}{50}} プロットしてみる from numpy import random import matplotlib.pyplot as plt import seaborn as sns sns.set() sns.kdeplot(random.normal(loc=50, scale=5, size=100000), label='normal') sns.kdeplot(random.binomial(n=100, p=0.5, size=100000), label='binomial') plt.legend() plt.show() おわりに 正規分布の式をテイラー展開してxを離散値として扱えばたぶん数学的に証明できる気がする。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

二項分布を正規分布の形で近似する

はじめに 二項分布と正規分布が一致する条件を調べる。 二項分布 P(X=k)={ }_{n} \mathrm{C}_{k} p^{k}(1-p)^{n-k} \quad(k=0,1,2, \cdots, n) についてp=1/2、n=100とすると P(X=k)= \frac{{ }_{100} \mathrm{C}_{k}}{2^{100}} \quad(k=0,1,2, \cdots, n) 正規分布 p(x)=\frac{1}{\sqrt{2 \pi \sigma^{2}}} e^{-\frac{(x-\mu)^{2}}{2 \sigma^{2}}} についてμ=50、σ=5とすると p(x)=\frac{1}{5\sqrt{2 \pi }} e^{-\frac{(x-50)^{2}}{50}} プロットしてみる from numpy import random import matplotlib.pyplot as plt import seaborn as sns sns.set() sns.kdeplot(random.normal(loc=50, scale=5, size=100000), label='normal') sns.kdeplot(random.binomial(n=100, p=0.5, size=100000), label='binomial') plt.legend() plt.show() 二項分布を正規分布の形に近似する スターリングの公式を使うと次の近似が成り立つ。 \frac{n !}{k !(n-k) !} p^{k}(1-p)^{n-k} \approx \frac{1}{\sqrt{2 \pi n p(1-p)}} \exp \left\{-\frac{(k-n p)^{2}}{2 n p(1-p)}\right\}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【StyleGAN2】のアーキテクチャ!!

本記事では、リアルな画像を生成できるStyleGAN2について説明します(CNNやGANの基礎を理解している前提で記載しています。まだ理解していない方は別冊のCNNやGANの基礎を先に読んでいただけると理解しやすいと思います)。 また、どんなものなのかを実際に体験するための手順も別記事にて投稿しています。興味のある方は、こちらも見ていただければ幸いです。 0.はじめに StyleGANは、高解像度の画像生成可能なProgressiveGANをベースに画像の画風変換(Style Transfer)を取り入れたアルゴリズムです。また、StyleGAN2は、StyleGANの課題を改善したアルゴリズムです。そのため、StyleGAN2を理解するために、ProgressiveGAN、StyleGAN、StyleGAN2と順を追って理解を進めていきたいと思います。 1 ProgressiveGAN(PGGAN) (1) PGGAN とは PGGANは、GANにおける高解像の画像生成の以下3つの課題を回避することで高解像度の画像生成を可能にしたアルゴリズムです。 高解像度の画像では、正解画像と偽物画像との識別がDiscriminatorで容易となり、GeneratorとDiscriminatorの学習が十分に行えない。 高解像度の画像を扱うので通常より多くのメモリが必要になる。そのため、より小さなミニバッチサイズで学習を行ってしまうと学習が不安定になる。 学習に時間がかかる。 基本コンセプトは、「GeneratorとDiscriminatorを、より簡単な低解像度の画像から段階的に学習させて、学習の段階が進むにつれて高解像度の情報を持つ新しい層を追加してく」というものです。これにより、GANの高解像の画像生成の課題の回避と、学習の高速化が可能になっています。 (2) PGGANのアーキテクチャ PGGANのアーキテクチャを図1に示します。 図1.ProgressiveGANのアーキテクチャ Progressiveなネットワークの構成と学習(Progressive growing of GANs) 以下のように4×4の画像から1024×1024の画像まで、段階的(Progressive)な処理で行われます。 ①Training Progresses 1 まず4×4の低解像度で従来のGAN構造に従って、学習が安定化するまで十分にGeneratorとDiscriminatorの学習を行います。 ②Training Progresses 2 次にGeneratorにアップサンプリングを行うnearest neightborフィルタリング(単純に隣からピクセルの値をコピーして拡大します)と、2つの逆畳み込み層を追加したネットワークで、8×8の解像度で学習が安定化するまで十分にGeneratorとDiscriminatorの学習を行います。 ③Train Progresses 3~9 ①②同様の処理を、16×16(3層目)、32×32(4層目)、、、、1024×1024(9層目)の解像度と、それに対応するために追加されるアップサンプリング用のnearest neightborフィルタリングと逆畳み込み層、及び、ダウンサンプリング用のaverage poolingと畳み込み層で段階的に行っていきます。 学習の実装について 学習においては、以下のような実装があります。 ①Training Progresses1~8を1エポック間に渡って学習し、最後のTraining Progresses9を残りのエポックを学習させる。 ②1エポック目(Training Progresses1)、2エポック目(Training Progresses1~2)、3エポック目(Training Progresses2)、4エポック目(Training Progresses2~3)、、、15エポック目(Training Progresses8~9)、16~最終エポック目(Training Progresses9)で学習させる。 Progressiveなネットワークの効果 画像生成タスクにおいては、画像の大域的特徴量と局所的特徴量の両方を捉えることが重要になりますが、Progressiveなネットワークでは単に画像を高解像度化するだけではなく低解像度の処理で画像の大域的特徴量を先に捉え高解像度の処理で局所的特徴量を捉えるという効果もあります。 (3)高解像度の画像生成のための工夫 A) なめらかな解像度の変換 PGGANでは、上述のように画像の解像度をnearest neightborフィルタリングとaverage poolingとを用いて、それぞれ2倍、1/2倍の倍々でアップサンプリングやダウンサンプリングしていきますが、ある解像度で十分に学習させたネットワークであっても、この倍々処理を急激に行うと非なめらかな変化を生じさせてしまいます。そのためPCGANではアップサンプリングおよびダウンサンプリングのための新しい畳み込み層をネットワークに追加するときに、画像のRGB値を線形補間によって、なめらかに変化させるようにしています。 図2.線形補間の説明 図2は線形補間によるなめらかな解像度の変換の様子を、16×16->32×32での切り替え時の例を示したものです。 (a) : 遷移開始時の様子。特に線形補間は行われません。(α=0) (b) : 遷移途中の様子。 0 < α < 1 を補間係数として、Generator(G)で、特徴ベクトルから変換したRGB情報(toRGB)を、それぞれ toRGBout = (1 – α) × toRGBin1 + α × toRGBin2 で混ぜ合わせます。そして学習が進むにつれて、α = 0 -> 1 とすることでなめらかな線形補間を行います。 (c) : 遷移完了時の様子。 線形補間も完了しています。 (α=1) この遷移(α=0->1)は、たとえば1エポック間の学習を通じて行うようにします(1エポックの開始時はα=0、1エポック完了時はα=1)。 B) 生成画像の多様性向上とモード崩壊防止のための工夫 PGGANでは、前述のような高解像度の画像生成のための工夫だけでなく、minibatch standard deviation(簡略化されたminibatch discrimination)を用いて生成画像の多様性向上とモード崩壊防止のための工夫もしています。 GANの課題の一つとしてモード崩壊(学習が不十分な識別器に対して生成器を最適化した場合や、生成器への入力ノイズzの潜在変数としての次元が足りていない場合などにおいて、生成器による生成画像がある特定の画像に集中してしまい、学習用データが本来持っている多様な種類の画像を生成できなくなってしまうこと)があります。Minibatch discriminationでは、このモード崩壊を防止するために、discriminatorにミニバッチデータ内のデータの多様性を検出できる仕組みを導入しています。PGGANでは、このminibatch discriminationの仕組みを大幅に簡略化した仕組みであるminibatch standard deviation を導入することで、minibatch discriminationのように、新しいハイパーパラメータを必要とすることなく、生成データの多様性を向上させモード崩壊を防止しています。 ⅰ) ミニバッチデータ内の各画像データの各ピクセル値(RGB値)に対して、同じ位置に対応しているピクセル値(RGB値)で標準偏差を計算する。 ⅱ) すべての高さ(H)と幅(W)チャネル数(C)で、この各ピクセル(=標準偏差)を平均化しスカラー値を取得する。 ⅲ) 得られるスカラーをコピーし結合することで、1つのH×Wのテンソル(=特徴マップ)を取得する。 C) 学習の安定化のための工夫 一般的にGANにおいては学習を安定化させることを目的として、batch normalizationの仕組みがネットワークに導入されています。しかしながら、PGGANではbatch normalizationによる学習安定化効果は見られないとして、別の学習安定化のための工夫を使用しています。 ①Equalized learning rate ※重み更新に対する工夫 一般的にディープラーニングにおいては学習率が減衰するような最適化アルゴリズム(Adam等)を用いてネットワークの重みを更新します。PGGANではこのような重みの更新ではなく、以下に示すようなもっと単純な方法でネットワークの重みを更新します。 ⅰ)GeneratorとDiscriminatorのネットワークの各層(ⅰ)の重み wi の初期値を、wi ~ N(0, 1) で初期化する。 ⅱ)初期化した重みを、各層の実行時(=順伝搬での計算時)に、以下の式で再設定する。 \hat{w_{i}}=w_{i}/c            (標準化定数 c = √2/層の数) ②Pixelwise feature vector normalization in generator ※データに対する工夫 GeneratorとDiscriminatorの不適切な競争の結果として、GeneratorとDiscriminatorからの出力信号(=偽物画像と判別結果)が制御不能になることを防止するために、PGGANではGeneratorの各畳み込み層の後の中間層からの出力(=特徴ベクトル)に対して「各ピクセル毎」に以下のような特徴ベクトルの正規化処理を行います。 b_{x,y}=a_{x,y}/\sqrt{\frac{1}{N}\sum_{j=0}^{N-1}(a^j_{x,y})^2+\epsilon}            x, y : ピクセル座標 ax, y :生成器の中間層から出力される特徴ベクトル(正規化前) bx, y : 正規化後の特徴ベクトル ε = $\mathbf10^8$ : ネットワークの重み N : 特徴マップ数 この正規化手法により、(batch normalizationと同様にして)学習の際の変化の大きさに応じて信号を増幅または減衰ができるようになるので、結果として学習の安定化が得られます。 2 StyleGAN (1) StyleGAN とは StyleGANは、ProgressiveGANをベースに非常にクオリティの高い画像を高解像度で生成できます。また、単に高解像度の画像を生成できるだけでなく、人物画像の高レベルで大域的な属性(顔の輪郭、眼鏡の有無など)から、局所的な属性(しわや肌質など)まで切り分けをアルゴリズムで制御できるようになっています。 (2) StyleGANのアーキテクチャ StyleGANの全体アーキテクチャとGeneratorの詳細なアーキテクチャを図3に示します。 図3.StyleGANの全体アーキテクチャとGeneratorの詳細なアーキテクチャ図 このStyleGANは以下の特徴を持ちます。 ①Mapping network f ②Adaptive Instance Normalization(AdaIN) ③Pixel-wise Noise InputとStochastic variation ④progressiveGANから入力層を排除 (3) StyleGANの特徴 ①Mapping network f(潜在空間zから潜在空間wへの写像と潜在表現の獲得) 従来のGANでは、入力ノイズ z ∈ Z (Z:潜在空間)をそのままGeneratorに入力していましたが、この方法では図4(B)に示すように画像の特徴量の一部の組み合わせが存在しないケースにおいて、入力ノイズzが存在する潜在空間Zは、線形ではなくentanglement(もつれ)のある歪んだ空間となります。 図4 2つの変動要因(例えば、男らしさと髪の長さ)を用いた例 このようなentanglement(もつれ、歪み)のある空間では、潜在空間のある部分空間からサンプリングしたベクトル(図4では特徴軸上のベクトル)だけを使って画像を生成したときには、複数の要素が同時に変動してしまいます(ex. 性別や肌色などが同時に代わる)。 ※一方で、disentangle(解きほぐし)されている潜在空間では、潜在空間のある部分からサンプリングしたベクトルだけを使って画像を生成したときには変動する要素が1つのみであるため意図している画像の生成がしやすくなります。 このような問題に対処するために、StyleGANでは潜在変数である入力ノイズzをmapping network fという8層のMLP(多層パーセプトロン)で構成されるネットワークに入力し、中間的な潜在空間Wへと写像して、別の潜在変数w∈Wを獲得した上で後段のSynthesis Networkに入力します。Mapping networkは学習されるネットワークであるため潜在変数wは図4(C)に示すように、画像の特徴量のentanglementの少ない状態で最適化されることが期待できます。 ②Adaptive Instance Normalization(AdaIN)(各解像度スケールでのAdaINの採用) AdaINは、Instance Normalizationの発展版で1つのモデルであらゆるスタイル(画風)への変換を可能にした正規化手法です。StyleGANにおいてAdaINは、Mapping Networkによって得られた中間潜在変数wに対して、アフィン変換(平行移動変換)を施した後に、このアフィン変換によるスケール値とバイアス値を、それぞれのAdaINの制御パラメータであるスケール項ys,i とバイアス項 yb,i として用います。 AdaINは、以下の式で与えられます。 AdaIN(x_{i},y)=y_{s,i}\frac{x{i}-\mu(x_{i})}{\sigma(x_{i})}+y_{b,i} xi : 特徴マップ(content) y = (ys,i, yb,i) : 中間潜在変数wをアフィン変換して潜在ベクトル(Style) ys,i : スケール項(パラメータ1) yb,i : スケール項(パラメータ2) このAdaINの処理は、特徴マップ単位(=チャンネル単位)での正規化処理になっていますが、このAdaINによる変換を各解像度スケール(4×4、8×8、、、1024×1024)の畳み込みの後に行うことで、各解像度での画像全体に渡って大域的にスタイルを変化させることができるようになります。 ③Pixel-wise Noise InputとStochastic variation(ピクセル単位のノイズ入力と確率的変動) 従来のGANでは局所的な特徴生成も大局的な特徴生成と同様に潜在変数としての入力ノイズを直接Generatorに入力することで実現するアーキテクチャになっていましたが、前述の潜在空間の歪みによりノイズの影響を制御することが困難でした。 StyleGANでは、Synthesis networkでの各畳み込みの直後にノイズマップでピクセル単位のノイズを直接加えることで局所的な特徴生成を直接制御することを可能にしています。 ※前述のAdaINが大域的なスタイルの変化、ノイズマップが局所的なスタイルの変化を制御します。ノイズの影響と効果については図5のようになります(論文より)。 図5 各ピクセルの標準偏差 (a)ベースとなる生成された画像 (b)ベースとなる画像はそのままで、それぞれノイズを変えたときの4つの画像 (c)ノイズの影響を受けている個所をグレースケールで表示した画像(白が影響大)、 髪の毛やシルエット、背景の一部、目の反射にも影響しているが、アイデンティティやポーズといった大域的な特徴には影響を与えていません。 図6は、Synthesis Networkのどの層にノイズを加えるかで生成画像の効果を示した図です(論文より)。 図6 ジェネレーターの各層に入力されたノイズの影響 (a)全ての層にノイズを加えた場合 (b)ノイズを加えない場合 (c)高解像度の層(64×64~1024×1024)のみにノイズを加えた場合 (d)低解像度の層(4×4~32×32)のみにノイズを加えた場合 ノイズを加えないと全体的に均一で立体感のない画像が生成されています。ノイズを加えると肌質などの局所的で細かなテクスチャーが加味されてよりリアルな画像が生成されています。そしてノイズを加える層で、粗いノイズ(低解像度)では髪の毛が大きく巻いていたり背景が大きく見えたりしますが、細かいノイズ(高解像度)では髪の毛の巻具合や背景の細かさ肌の毛穴などが浮かび上がってきます。 ④progressiveGANから入力層を排除(代わりに学習済み定数マップを入力) ProgressiveGANでは、乱数を生成してGeneratorへの初期入力(4×4)としています。 一方StyleGANでは入力層を排除し、学習済み定数マップ(4×4×512)を入力します。これは、StyleGANが生成画像を中間潜在変数wとAdaIN、そしてノイズマップによって制御しており可変な入力が不要であるためです。 (4) Style Mixing Style Nixingは、潜在空間よりサンプリングした2つの潜在変数z1、z2 ∈ Z 、及び2つの中間潜在空間 w1、w2 ∈ W に関してSynthesis NetwrokのAdaINのパラメータとして入力する際に、ある解像度スケールまでは z1・w1 を入力し、それ以降の解像度スケールには z2・w2 を入力するように切り替えます。こうすることでSynthesis Networkは、隣接した解像度スケールの層間で画風(Style)に相関があるように学習しなくなるので、画風の影響を各解像度スケールの層に局所化させることが可能になります。 図7は2つの中間潜在変数を切り替えたときの例になります(論文より)。 低解像度(4×4 ~ 8×2)で中間潜在変数をw1->w2に切り替えると、ポーズやヘアスタイル、顔の形、眼鏡などの大域的な特徴がソースBからもたらせられますが、すべての色(目、髪、照明)と細かい顔の特徴はソースAに似ています。代わりに中間解像度(16×16〜32×32)で中間潜在変数をw1->w2に切り替えると、より小さなスケールで顔の特徴やヘアスタイル、目の開閉がソースBから継承されますが、ポーズや一般的な顔の形状、眼鏡はソースAから継承されます。 最後に、高解像度(64×64 ~ 1024×1024)で中間潜在変数をw1->w2に切り替えるとソースBから主に配色やピクセル単位の局所的な特徴が引き継がれます。 図7.中間潜在変数を切り替えたときの例 (5) 潜在空間におけるentanglement(もつれ)とdisentanglement(解きほぐし)の評価 StyleGANでは、entanglement/disentanglementを定量化する指標として、Perceptual path length(PPL)とLinear separabilityの2つの指標を使っています。 (ⅰ)Perceptual path length(PPL) 潜在空間ベクトルの補間により、画像に驚くほど非線形の変化が生じる可能性があります。 StyleGANでは、このような線形補間においてどれだけ急激に画像が変化するのかをPPLで計測します。PPLは文字通り「人間の知覚的」に潜在空間上で画像がなめらかに変化するのかの指標です。 2つの潜在変数 z1、z2 を比率tで混ぜ合わせた潜在変数で生成した画像と、比率t+εで混ぜ合わせた潜在変数で生成した画像の距離の期待値となっています。実態はVGG(画像認識ネットワーク)を使用し、それぞれが算出する特徴量ベクトルの距離を「知覚的」な距離としています。解析的には求めることはできないので、多くの画像で計算を行って期待値をとった結果がPPLの値になります(値が小さいほど潜在空間が知覚的になめらかだということになります)。 潜在空間ZでのPPL G:生成器の出力(生成画像) d:画像間の距離を測る距離関数 slerp:球面上の線形補間 ε:$\mathbf 10^{-4} $(微小ノイズ) 中間潜在空間wでのPPL G:生成器の出力(生成画像) d:画像間の距離を測る距離関数 lerp:直線上の線形補間 f:Mapping Networkによる写像 (ⅱ) Linear separability disentanglement(解きほぐし)されている潜在空間では、潜在空間のある部分からサンプリングしたベクトルだけを使って画像生成したときは、生成された画像で変動する要素は1つのみである。これは見方をかえると潜在空間の各点が線形分離可能であることを意味している。そのためStyleGANでは、潜在空間内の点がどれだけうまく線形分離可能であるかを測定することでentanglement/disentanglementの定量的指標として利用しています。 ※興味がある場合、詳細なロジックについては論文を参照してください 3 StyleGAN2 (1) StyleGAN2とは StyleGAN2は、StyleGANで課題となっていたdropletと呼ばれるノイズが生じる問題(図8)や生成画像の特徴の一部が不自然になる事象(図9)を、アーキテクチャを見直して解消したものです。 dropletと呼ばれるノイズが生じる問題 図8.dropletの発生の例 生成画像の特徴の一部が不自然になる事象 図9.生成画像の特徴の一部が不自然になる例(顔の向きに関わらず正面を向いて生成される歯) (2) StyleGAN2の特徴 StyleGAN2では、上記の課題を解決するために主に以下の3つの観点での改善が行われています。 (ⅰ)AdaINのように実際の統計量で正規化するのではなく、推定の統計量で正規化することで不要なモードの顕在化を防ぎ、「dropletを除去」する。 (ⅱ)Progressive Growingの代わりにskip connectionを持った階層的なGeneratorを用いることで、「生成画像の特徴の一部が不自然になる事象」を低減する。 (ⅲ)潜在空間をなめらかにすることで、画像品質を向上する。 (ⅰ) dropletの除去(論文2章:Instance normalization revisited) StyleGANでのAdaINは実際に入ってきたデータの統計量を使って正規化していますが、これがdropletの原因になっていました。この対策として実際に入ってきたデータの統計量ではなく、「推定の統計量」を使って畳み込みの重みを正規化することでdropletの発生を防げることがわかりました。実際には下記の図10のように対策しています。 図10.正規化処理の改善 図10(a)が元のStyleGANのアーキテクチャで、「A」はスタイルを生成する中間潜在変数wから学習されたアフィン変換を示し、「B」はノイズブロードキャスト操作です。図10(a)のAdaINを詳細に記述すると2つの手順に分解できます。1つ目はコンテンツ情報を自身の統計量で正規化(Norm mean/std)すること、2つ目は正規化されたコンテンツ情報をスタイル情報「A」を線形変換(Mod mean/std)することです。また、学習した重み(w)、バイアス(b)および入力を(c)としてボックスごとに1つのスタイルがアクティブになるように灰色のボックスをStyle blockとして詳細化したものが図10(b)になります。なお活性化関数(leaky ReLU)は、バイアスを追加した直後に常に適用されます。 次に図10(c)に記載いてあるように、実際の統計量である平均値(mean)を取り除き正規化の操作を標準偏差(std)による割り算のみに、スタイルの線形変換も係数の掛け算だけにします(図10①)。そしてノイズ挿入部はStyle blockの中にある必要はないのでStyle blockの外に出します(図10②)。 次に図10(d)で、最初のStyleベクトルによる線形変換を畳み込みの内部で実施します。Style block内ではMapping Networkで生成したスタイルベクトルwを線形変換したsi(ys,i)をかけています。コンテンツ画像にsiをかけたものを畳み込みの重みwijkで処理するという操作は、コンテンツ画像を重みwijkとsiの積で畳み込むということと等価なので、この操作は以下のように書き換えることができます(図10③)。 w'_{ijk}=s_{i}・w_{ijk} 最後に、正規化(norm)の処理(図10(C)では標準偏差で割っているだけ)を畳み込みの内部で実施するようにします。入力が標準正規分布に従っていると仮定する出力画像の標準偏差は重みのL2ノルムによってスケーリングされ、以下のようになります。 \sigma_{j}=\sqrt{\sum_{i,k}(w'_{ijk})^2} 正規化(norm)の処理では上述のように標準偏差で割っているだけでしたので、畳み見込んだ出力に標準変数の逆数をかければいいことになりますので、畳み込んだ出力に標準偏差をかける操作は、重みwijkに標準偏差の逆数をかけたもので畳み込むことと等価です。従って正規化(norm)の操作は以下になります(図10④)。 w''_{ijk}=w'_{ijk}/\sqrt{\sum_{i,k}(w'_{ijk})^2+\epsilon} 以上の見直しにより、Style block全体が単一の畳み込み層に実装されています。 この正規化の改善により、図11のように生成された画像からdropletが削除されるようになります(論文の図3より引用)。 図11.dropletの改善 (ⅱ) 生成画像の特徴の一部が不自然になる事象を低減(論文4章:Progressive growing revisited) Progressive growingは高解像度の画像生成は非常に成功したモデルですが、個々のGeneratorが独立しているので頻出する特徴を生成する傾向にあり、その結果として先述した顔が動いても歯が追従していかないという結果に繋がっています。そのためStyleGAN2ではStyleGANのネットワークの再構築しProgressive growingに代わる新たなアーキテクチャを探すにより高解像度の画像生成ができるようにしています。具体的には図12の3つのモデルを検証しています。 図12 3つの検証モデル 図12(a)はMSG-GANです。複数のスキップ接続を使用してGeneratorとDiscriminatorの同一の解像度を接続しています。図12(b)は各解像度のRGB出力をアップサンプリング(Discriminatorではその逆)して合計して設計を簡略化しています。図12(c)では、さらにresidual connectionを使うように変更しています。 これらを全部組み合わせて評価した結果、Generatorでは図12(b)をDiscriminatorでは図12(c)を採用しています(緑色で表示)。 このアーキテクチャを採用して生成された画像を図13に示します。顔の向きに合わせて歯や目も自然に合わせて動いているのがわかります。 図13 Progress growingの改善結果の画像 (ⅲ) 潜在空間をなめらかにすることによる画像品質向上(論文3章:Image quality and generator smoothness) 潜在空間の知覚的ななめらかさを表すPerceptual Path Length(PPL)と生成される画像の品質に相関があることがわかってきたのでモデルの正則化に組み込んでいます。 正則化項は以下になり、潜在変数の摂動に対するGeneratorの出力値の変換をなるべく小さくするように仕向けています。 ここでJwはヤコビ行列(Jacobian matrix)で、Jw = ∂g(w) / ∂w、yは正規分布のピクセル強度を持つランダム画像、aは定数ですが、||$\mathbf J^T_{w}y $||2 の移動平均によって動的に変化させながら学習を行い、それによって最適な値が学習中に設定されるようになっています。 また、正則化項の更新は計算コストとメモリ使用量を削減から更新頻度を下げる(16ミニバッチごとに1回の実行でも影響がないことが分かっているため)戦略をとっています(Lazy regularization)。 4 おわりに StyleGAN2はStyleGANでの問題を修正し、生成画像の品質をさらに向上させ、学習時のパフォーマンスの向上も実現しました(とはいえNVIDIA DGX-1(8GPU)でFFHQの学習データで9日かかるみたいです)。 実際に生成される画像も本物と区別がつかないくらいすばらしいものですので、学習済みモデルからの画像生成をコードを修正してPC(CPU)でも実行できるようにしていきたいと思います。 【参考文献、サイト】 ・論文 ・Analyzing and Improving the Image Quality of StyleGAN ・情報工学機械学習生成モデル.md ・GANの基礎からStyleGAN2まで 本書は筆者たちが勉強した際のメモを、後に学習する方の一助となるようにまとめたものです。誤りや不足、加筆修正すべきところがありましたらぜひご指摘ください。継続してブラッシュアップしていきます。また、様々なモデルの解説書を掲載していますので、興味のある方は、以下のサイトもご参照ください。 © 2021 NPO法人AI開発推進協会
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python で html img タグに base64 で画像を埋め込む

概要 png とか jpg 画像を読み取って、 base64 形式の img タグを吐き出す python コードスニペット。 (自分用メモ) コード def get_base64_image_tag(image_path, mime_type): with open(image_path, "rb") as f: src = base64.b64encode(f.read()).decode('utf-8') return f'''<img src="data:image/{mime_type};base64,{src}" />''' 使用例 画像名 画像 aaa.png bbb.jpg >>> tag = get_base64_image_tag("aaa.png", "png") >>> tag '<img src="data:image/png;base64,iVBORw0KG[...]kSuQmCC" />' >>> tag2 = get_base64_image_tag("bbb.jpg", "jpeg") >>> tag2 '<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQE[...]/r5f1uf/9k=" />' 参考 Data URI scheme (Wikipedia) base64 --- Base16, Base32, Base64, Base85 データの符号化
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「画像認識ウマ娘イベント選択肢チェッカー」をPythonで作るチュートリアル

はじめに ウマ娘、流行ってますね (ひと波超えた感はありますが)。 特に流行りがピークだった4月頃にこんなツールを見かけて、「自分もいつかこんなツールが作れるようになりたい…!」と思った方も多いのではないでしょうか。 自動でDMM版ウマ娘のウィンドウから、イベント選択肢の効果を表示するツールを作ったダウンロードはこちらからhttps://t.co/V8DRfoBaDgreadmeはこっちhttps://t.co/yQJiDMbhza pic.twitter.com/DYnCVUiNCw— amate (@amatetest) April 16, 2021 このツールはC++で書かれていますが、根幹部分である、画像認識、OCR、イベント選択肢の表示の機能は、実はPythonを使えば結構簡単に実現することができます。 というわけで今回は、上記の3機能に焦点を当てて、順を追って作っていきたいと思います。 今回のゴール 1. 機能の整理 プログラムを書く前にまずは、 ・どんな機能を付けるか  - 最低限実現したい機能は何か ・それはどうすれば実装可能か  - 使えそうなライブラリはあるか  - 情報は自分で入力するか、どこかから拾ってきて整形するか などを整理します。 今回であれば、 ・どんな機能を付けるか  - GUIは省略、コマンドラインに直出力  - ウィンドウ位置を特定する  - イベント名をOCRで抽出する  - イベント選択肢の効果を出力する ・それはどうすれば実装可能か  - PyAutoGUIの画像認識(locateOnScreen)を使えばウィンドウの位置が判りそう!  - PyOCR+Tesseractで画像から日本語を含む文字列を抽出できそう!  - GameWithのイベント選択肢チェッカー(逆引き検索ツール)の情報が使えそう! といった具合ですね。 2. アタリを付けた構想から具体的な機能を作ってみる せっかくPythonを使っているなら、こういった開発を行う場合はJupyter Notebook(Lab)の使用を強くおすすめします。 ・ウィンドウ位置の特定 PyAutoGUIのlocateOnScreenは、画面内から入力した画像に一致する部分を探し出し、一致する部分があればその場所を返してくれる非常に便利な関数です。 ウマ娘のアプリウィンドウで画面変遷に依る変化のない部分はあるでしょうか? この部分は変わらなそうですね。 スクリーンショットからこの部分を切り出してtitlebar.pngとして実行ディレクトリに保存してみます。 すると画面からタイトルバーを見つけ出す関数は以下のようになります。 import pyautogui titlebar_pos = pyautogui.locateOnScreen('titlebar.png', confidence=0.8) # confidenceは正確さ(小さくすると多少の違いを吸収できる) print(titlebar_pos) 驚異的な簡単さですね。 このような高度な機能を、2, 3行で享受できるのがPythonの最大の強みだと個人的には思っています。 さて、これでウィンドウ位置が特定できました。と言いたいところですが、ウマ娘のアプリはウィンドウサイズを変更することができます。 このままでウィンドウサイズの変更に対応できるでしょうか。 試しにウィンドウを小さくしてみましょう。 先程と比較するとタイトルバーの空白部分が詰まっていますね。 locateOnScreen関数はこのような変化を吸収できないので、もう一捻り工夫が必要そうです。 ウィンドウサイズの変更の前後でよく観察してみると、タイトル部分と「X」などのアイコンサイズは変化がなさそうです。 ここで、locateOnScreen関数の戻り値を見てみると、 out Box(left=1730, top=398, width=172, height=45) (左上の座標(左), 左上の座標(上), 幅, 高さ)となっていることが分かります。 つまり、一致した部分の上下左右のカドの座標は足し算で求められるので、 左上のこの部分の左上の座標と、 右上のこの部分の右上の座標を 知ることで、「ウィンドウの位置」と「ウィンドウサイズ」の両方を得ることができそうです。 上の2画像をそれぞれumatitle.png, x.pngと名付けたとすると、 import pyautogui pos = pyautogui.locateOnScreen('umatitle.png', confidence=0.8) x_pos = pyautogui.locateOnScreen('x.png', confidence=0.8) print(pos[0]+pos[2]) # タイトルバーの下のアプリ描画部分の左上の座標 print(x_pos[0]+x_pos[2]-pos[0]) # ウィンドウ幅 これで、ウィンドウ位置、ウィンドウサイズを特定することができました。 ・イベント名をOCRで抽出する OCRをかける前段階として、 ・文字部分の切り取り ・2値化など、認識精度を上げるための前処理 を行う必要があります。 まずは、文字部分の切り取りから行っていきましょう。 - 文字部分の切り取り どのように目的部分を切り取るか戦略を立てるべく、今回の目的であるイベント選択画面を観察してみましょう。 プレイしている方ならすぐに気付くと思いますが、イベントタイトルは決まった位置に表示されています。 先程までで、ウィンドウの位置、サイズは取得できていますので相対座標で切り取れそうですね。 ウィンドウサイズ(幅)を乗算すれば常に狙った位置を切り取れるように、ウィンドウ幅を基準に規格化しておきましょう。 PyAutoGUIには指定した長方形領域のスクリーンショットを撮る関数も、そのままscreenshotという名前で用意されていて、引数としてregion=(左上の座標(左), 左上の座標(上), 幅, 高さ)を渡すことで領域を指定できます。 pos = pyautogui.locateOnScreen('umatitle.png', confidence=0.8) x_pos = pyautogui.locateOnScreen('x.png', confidence=0.8) width = x_pos[0] + x_pos[2] - pos[0] text_pos = (np.array([0.15, 0.33, 0.6, 0.065]) * width).astype(int) pyautogui.screenshot(region=(pos[0] + text_pos[0], pos[1] + pos[3] + text_pos[1], *text_pos[2:])) ↓ 目的の部分は切り出せましたので、次は認識精度を上げるための前処理を行っていきます。 - OCRの認識精度を上げるための前処理 今回の場合、文字の背景は軽いグラデーションが掛かっている程度ですので、一番簡単な「閾値による2値化」を採用したいと思います。 「閾値による2値化」を行うというのはものすごく単純な話で、画像をモノクロにしたとき、ある値より暗ければ真っ黒、明るければ真っ白にするという処理のことです。 今回はcv2というライブラリを使って2値化を行ってみたいと思います。 先程のスクリーンショットの結果がpicに格納されているとして、 import cv2 import numpy as np gray = cv2.cvtColor(np.array(pic), cv2.COLOR_BGR2GRAY) _, th = cv2.threshold(gray, 210, 255, cv2.THRESH_BINARY) th ↓ さらに、白黒を反転して、 255 - th ↓ きれいに加工することができましたので、いよいよOCRをかけてみたいと思います。 - OCRによる文字列抽出 今回はPyOCR+Tesseractを採用して文字を抽出してみます。 それぞれ下記のページなどを参考にインストールを行ってください。使い方に関しても参考になると思います。 Tesseract+PyOCRで簡易OCRを試してみる PythonとTesseract OCRで文字認識 import pyocr tool = pyocr.get_available_tools()[0] text = tool.image_to_string(Image.fromarray(255 - th), lang='jpn', builder=pyocr.builders.TextBuilder()) print(text) トレーナー心得「指導は常に改良せよ』 ほぼ完璧に抽出できました。 ここまで来ればほとんど終わったようなもので、あとはイベント名と効果のセットを用意して出力すれば完了です。 ということで、次はデータの収集を行います。 イベント選択肢の効果を出力する 出力の前に、イベント名と効果がセットにっているデータがないと始まりませんので、データの収集方法を模索します。 - データ収取 今の時代ゲームの攻略サイトは多いので、それらのいずれかから拝借できないものかと考えながら色々見て回っていると、GameWithさんに良さそうなサービスがありました。 イベント選択肢チェッカー(逆引き検索ツール) Chromeの検証機能を使って通信を監視してみると、入力欄に文字を入力しても一切通信を行っていないことが分かり、これによってまとまった辞書をローカルにダウンロードしていることがほぼ確定しました。 もう少し探索してみると、目的の辞書が定義されたjavascriptファイルを発見、今回はこれを使ってみようと思います。 (あまりウェブサイトの内部ファイルをピンポイントで書き出すのもいかがなものかと思うので、ここでは省略) これを{イベント名: 選択肢・効果}の形に整形し、保存しておきます。 - 表示するイベントのピックアップ OCRでは高い確率で文字列を完璧には抽出できないので、何かしらの方法で近い名前のイベントをピックアップする必要があります。 ここでは標準ライブラリのdifflibからSequenceMatcherを使って文字列同士の類似度を計算します。 Pythonで文字列部分一致度合いを調べる 最も近い文字列のスコアが、ある一定の値より大きくなった場合にイベントと判断し、出力するという方針で書いてみたいと思います。 # 変数名が適当で申し訳ない from difflib import SequenceMatcher eventDatas = {イベント名: 選択肢・効果} # イベントの情報が入った変数 t = '' r = 0 for i in eventDatas: rr = SequenceMatcher(None, i, text).ratio() if r < rr: r = rr t = i if r >= .625: print(f'【{t}】') for i in eventDatas[t]: print(i['n'], ' ' + '\n '.join(i['t'].split('[br]')), sep='\n') out 【トレーナー心得『指導は常に改良せよ』】 あまり無理はなさらずに 体力+10~14 スキルPt+15~18 桐生院トレーナーの絆ゲージ+5 ※サポート効果により数値が変動 『トレーナー白書』見てみたいです スピード+5~6 賢さ+5~6 桐生院トレーナーの絆ゲージ+5 これで、ウィンドウ認識からイベント選択肢の効果の出力までを実装できました。 最後に、一つのクラス・プログラムにまとめたものを掲載したいと思います。 3. 最終的に1つのプログラムとしてまとめる umamusume_options_checker.py import pyautogui import numpy as np import time import pyocr from PIL import Image from requests import get from difflib import SequenceMatcher import cv2 class uma_option_checker: def __init__(self, title_ref, x_ref): self.pos_ref = title_ref self.x_ref = x_ref self.db_init() self.get_win_pos() self.tool = pyocr.get_available_tools()[0] self.scene = "" def db_init(self): male = ( get( "https://gamewith-tool.s3-ap-northeast-1.amazonaws.com/uma-musume/male_event_datas.js" ) .text.replace(";", "") .replace("window.eventDatas['男'] = ", "") ) """female = ( get( "https://gamewith-tool.s3-ap-northeast-1.amazonaws.com/uma-musume/female_event_datas.js" ) .text.replace(";", "") .replace("window.eventDatas['女'] = ", "") )""" eventDatas = eval(male) # eventDatas = eval(female) self.eventDatas = {i["e"]: i["choices"] for i in eventDatas} def get_win_pos(self): pos = pyautogui.locateOnScreen(self.pos_ref, confidence=0.9) x_pos = pyautogui.locateOnScreen(self.x_ref, confidence=0.9) if pos is None or x_pos is None: self.pos = 0 return False else: self.pos = pos self.ratio = x_pos[0] + x_pos[2] - pos[0] self.text_pos = (np.array([0.15, 0.33, 0.6, 0.065]) * self.ratio).astype( int ) return True def get_scene(self): self.p = pyautogui.screenshot( region=( self.pos[0] + self.text_pos[0], self.pos[1] + self.pos[3] + self.text_pos[1], *self.text_pos[2:], ) ) gray = cv2.cvtColor(np.asarray(self.p), cv2.COLOR_BGR2GRAY) _, th = cv2.threshold(gray, 210, 255, cv2.THRESH_BINARY) text = self.tool.image_to_string( Image.fromarray(255 - th), lang="jpn", builder=pyocr.builders.TextBuilder() ) self.text = text if text: return True else: return False def get_likely(self): t = "" r = 0 for i in self.eventDatas: rr = SequenceMatcher(None, i, self.text).ratio() if r < rr: r = rr t = i return t if r >= 0.625 else self.scene def show(self): print(f"【{self.scene}】") for i in self.eventDatas[self.scene]: print(i["n"], " " + "\n ".join(i["t"].split("[br]")), sep="\n") print() def update(self): if self.get_win_pos(): if self.ratio > 0: if self.get_scene(): t = self.get_likely() if t != self.scene: self.scene = t self.show() if __name__ == "__main__": uop = uma_option_checker("umatitle.png", "x.png") while 1: time.sleep(0.5) uop.update() 改善点の候補 ・GUIの開発 ・現状では育成ウマ娘の情報を考慮していない為、イベント名が共通のイベントは異なるウマ娘の選択肢が表示される点の解決 ・OCR精度の向上 興味のある方は是非挑戦してみてください。 さいごに Pythonでは豊富なライブラリのおかげで、一見するととても高レベルな内容も短いコードで簡単に実現することができます。 もっと色んな人に気軽に使えるツールだよって伝えたい…
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む