20190413のPythonに関する記事は20件です。

GitHubのWebhookでプルリクエストをマージした際にツイートできるようしてみた

はじめに

こんにちは。なおとです。
今回はGitHubのプルリクをマージした際に自動でツイートできるようにしたのでその解説となります。

プルリクをマージするとこんな感じでTweetされます。


なぜやろうと思ったのか?

現在数人で個人開発をしていますが、時間の制約もあり、かつ非同期のコミュニケーションのため、モチベーションを保つことは難しいです。

普段はGitHub↔︎slack連携をしているので、イシュー作成やプルリクをマージした際に通知がきます。
せっかくプロジェクトもpublicにしているので、ツイッターのオープンな場に通知できたらモチベーションも上がるのでは?と考えたのがきっかけです。

また、今の個人開発ではTwitter APIを使用しています。
以前アカウントを新規取得したのですが、ツイートをしなかった影響なのかアカウントをロックされてしまいました。
その為、CT(ケイゾクテキ ツイート)をして、アカウントがロックされないようにしよう!!と思ったのもあります笑

今回のソースコードはこちらにあります。
https://github.com/nsuzuki7713/github-webhook

構成

こちらがシーケンス図となります。

IMG_4582.png

シンプルな処理形式だと思います。

  1. ユーザーがプルリクを作成する
  2. GitHubのWebhookでAPI Gatewayにリクエストをする
  3. API Gatewayはlambdaを実行する
  4. lambdaでは最初に認証チェック、妥当性チェックを行う
  5. lambdaからtwitter apiを叩いてツイートする

開発

ツイッターアカウント作成とapiキーの作成

APIからTweetするためには、Twitter APIが必要です。
下記記事を参考にして申請しました。
https://www.torikun.com/2018/10/08/twitter-developer-api/

特に詰まることなく、承認も一瞬でした。数日待たされるという記事も見かけましたが僕は申請したら、数分で承認メールがきました。

英語で使用用途を記載する必要がありますが、僕はGoogle翻訳をフル活用しました。

lambdaとAPI Gatewayの設定

下記記事を参考にして、進めました。
スクショもあり、丁寧に手順が書いてあります。
【API Gateway】AWS Lambda統合のPythonでHello, world

GitHub webhookの設定

下記記事を参考にして、進めました。
Github serviceをwebhookに変更した

こちらが設定した内容です。
webhookのtriggerを設定できますが、今回はPull requestsにチェックを入れます。

スクリーンショット 2019-04-13 14.53.38.png

スクリーンショット 2019-04-13 14.54.23.png

lambda側の実装

今回はpythonを使用しています。選定理由はtwitter apiを使用したサンプル記事が多かったからです。

コードの詳細はGitHubをご確認お願いします。
https://github.com/nsuzuki7713/github-webhook

フォルダ構成

最低限必要なファイル構成となります。

最初、使用するモジュールをmodulフォルダの中に入れていましたが、Lambdaはデフォルトでプロジェクト直下にモジュールを置く必要があるとのことです。
プログラム初心者がAWS Lambda(Python)でハマった7個のこと

├── function.py
├── settings.example.py
├── (以下、使用するモジュール)

ライブラリのインストール

# プロジェクトの直下に移動

# ライブラリのインストール
$ pip3 install requests requests_oauthlib -t .

僕はpip3 install requests requests_oauthlib -tで下記エラーがでました。

distutils.errors.DistutilsOptionError: must supply either home or prefix/exec-prefix -- not both

下記記事を参考にして、設定を変更しました。
http://www.sysop.jp/entry/2017/02/05/231821

APIキー等の設定

settings.py
###############Twitter API######################
CONSUMER_KEY = "Twitter APIのキー"
CONSUMER_SECRET = "Twitter APIのキー"
ACCESS_TOKEN = "Twitter APIのキー"
ACCESS_TOKEN_SECRET = "Twitter APIのキー"

###############GitHub Webhook######################
SECRET = "GitHubのWebhookで記載してsecretキー"

メイン処理の設定

Webhook payloadのexampleは公式ドキュメントが分かりやすいです。
https://developer.github.com/v3/activity/events/types/#pullrequestevent

function.py
#coding: UTF-8
import json,hashlib,hmac
from requests_oauthlib import OAuth1Session
import settings

def lambda_handler(event, context):
    # HMAC値による認証処理
    signature = event['headers']['X-Hub-Signature']
    signedBody = "sha1=" + hmac.new(bytes(settings.SECRET, 'utf-8'), bytes(event['body'], 'utf-8'), hashlib.sha1).hexdigest()
    if(signature != signedBody):
        return {"statusCode": 401, "body": "Unauthorized" }

    # プルリクの情報を抽出
    body = json.loads(event['body'])

    # actionのキーがなければ終了
    if "action" not in body:
        return {"statusCode": 200, "body": "exit" }

    # プルリクのクローズでなければ終了
    if body['action'] != "closed":
        return {"statusCode": 200, "body": "exit2" }

    # ツイートで必要な情報を取得
    title = body['pull_request']['title'] # プルリクのタイトル
    html_url = body['pull_request']['html_url'] # プルリクのURL
    user = body['pull_request']['user']['login'] # プルリク作成者
    merged_by = body['pull_request']['merged_by']['login'] # マージ者

    # GitHubのアカウントとツイートする際の名前の対応表
    user_list = {
        "nsuzuki7713": "なおと"
    }

    # tweet文章作成
    msg = user_list[merged_by] + "さんが"\
        + user_list[user] + "さんのプルリクをマージしました?" + "\n"\
        + "【" + title + "】となります?️" + "\n" +html_url

    # tweet処理
    twitter = OAuth1Session(settings.CONSUMER_KEY, settings.CONSUMER_SECRET, settings.ACCESS_TOKEN, settings.ACCESS_TOKEN_SECRET)
    params = {"status": msg }
    req = twitter.post("https://api.twitter.com/1.1/statuses/update.json",params = params)

    return {"statusCode": 200, "body": msg}

デプロイ

外部モジュールを使用する場合はlambdaのインライン上からはできなく、zip等にする必要があります。
今回はzip形式でデプロイしました。

# プロジェクトの直下に移動

$ zip -r toLambda.zip ./*

作成したzipファイルをlambdaのコンソールからアップロードします。

スクリーンショット 2019-04-13 21.17.09.png

確認

GitHubのWebHooks設定から実行して、確認することが可能です。

スクリーンショット 2019-04-13 15.13.23.png

スクリーンショット 2019-04-13 15.14.38.png

おわりに

API GatewayやWebhook、pythonなど初めて使うものが多く、まだ1つ1つ理解できていませんが完成はできました。

issue駆動開発をしたので、issueのクローズした順を追っていただければ僕の進め方も分かります。
https://github.com/nsuzuki7713/github-webhook/issues?q=is%3Aissue+is%3Aclosed

個人開発やGitHub駆動で学習している人は、CT(ケイゾクテキ ツイート)も作成してみませんか笑

ただ、今のままだと固定文言ですぐに飽きてしまうので、コミット数や変更行数のデータを見ながら、もっと文言のバリエーションとかを増やしたいなーと思っています。

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

#python の datetime で明日の 時・分をランダムに生成する例

>>> import datetime

>>> now = datetime.datetime.now()
>>> now
# datetime.datetime(2019, 4, 13, 18, 44, 57, 269002)

>>> rand_datetime_today = now.replace(hour=random.randint(1,23), minute=random.randint(1,59), second=0, microsecond=0)
>>> rand_datetime_today
# datetime.datetime(2019, 4, 13, 3, 8)

>>> rand_datetime_tomorrow = rand_datetime_today + datetime.timedelta(days=1)
>>> rand_datetime_tomorrow
# datetime.datetime(2019, 4, 14, 3, 8)

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/1238

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

pythonでcsvファイルの読み込み/書き出し/分析

今回はjupyternotebookで実行した結果に基づいて書きます。

読み込み

例えばワークディレクトリにあるsample.csvを読み込んでdata1とする場合、

untitled.py
import pandas as pd
data1 = pd.read_csv('sample.csv')

read_csv()を使うためにあらかじめパッケージpandasimportしている必要があります。pdとしてimportするのが慣例です。

書き出し

例えばワークディレクトリにdata1sample.csvとして書き出す場合、

untitled.py
data1.to_csv('sample.csv)

これでワークディレクトリにsample.csvが新たに保存されます。

分析

ロードしたデータの分析にはこれまたpandasの関数groupby()が役に立ちます。私が大学の課題などで使った時はこちらのサイトを参照しました。とてもわかりやすく書いてあります。

記事は以上です、ありがとうございました。


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

#python で 特定の年月日・時分秒・マイクロ秒指定で datetime オブジェクトを作成し、それを文字列にパースする簡単な例

datetime.datetime() を使う。

>>> import datetime
>>> datetime.datetime(2016,1,2, 10,20,5, 100).strftime('%Y-%m-%d %H:%M:%S %a %f')
'2016-01-02 10:20:05 Sat 000100'

ref

Python strftime() - datetime to string

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/1237

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

PyTorchで白黒人物画像に着彩するAIを作る

はじめに

 PyTorchでの人工知能制作にはまり、白黒画像に着彩してみたくなったので、やってみることにしました。細かい説明の前に、最終的な出力結果を紹介します。(人物画像はどれもGoogle画像検索で改変後の非営利目的での再使用が許可された画像です。)
ad.PNG
 パソコンのスペック上低画質の物しか扱えませんが、顔の部分を肌色に塗ることができています。ポイントは、ワイシャツの襟の部分などの白い部分は塗らないようになっているところです。今回はこのAIの仕組みと、簡単なソースコードの解説をしたいと思います。

原理解説

ここからはこのAIの仕組みについて解説します。

学習に使用する画像の読み込み&加工について

 これには画像処理系のライブラリのPython Imaging Library(PIL)を使用します。今回の目的は着彩AIを作ることなので、入力となる白黒画像と、正解データとなるカラー画像が必要になります。白黒画像はPILのconvert('L')から作成することができるため、画像データとして必要になるのは顔写真のカラー画像のみということになります。データセットにはLabeled Faces in the Wildを使用しました。また、これらの画像は読み込み後、切り抜きやリサイズなどの加工を施して最終的に50×50の画像になります。切り抜きについては後に説明します。

モデルについて

 今回は画像処理ということで、定番の畳み込みニューラルネットワーク(CNN)を用いました。と言っても、畳み込み層とプーリング層はそれぞれ一層ずつ、全結合層も一層という簡素な作りになっています。CNNを使う必要性はないかもしれませんが、私が以前作成したAI(これも画像処理系)で、CNNを使う場合と使わない場合で圧倒的にCNNのほうが学習が速かったので、CNNを使います(おそらく畳み込みとプーリングにより画像を圧縮したためだと思っています。今後検証します)。モデルの入力は50×50個のノード、出力は50×50×3個(RGBの3チャンネルなので)のノードとなっています。

学習の際の流れ

 今回のようなニューラルネットワークを用いたAIの学習の際には、

 1. モデルに何らかのデータを入力する
 2. モデルの出力と、正解との誤差を求める
 3. 誤差をもとにモデルを更新する
 4. 1.に戻る

という流れを踏むと思います。今回の場合も基本的に同じです。上の流れを今回作るAIで具体的に言い換えると、

 1. モデルに白黒画像を入力
 2. 出力画像とカラー画像との誤差を求める
 3. 誤差をもとにモデルを更新する
 4. 1.に戻る

という流れになります。入力の白黒画像は各ピクセルごとに0~1の値が格納されている配列です。出力のカラー画像はRGB形式で、これは各ピクセルごとにR,G,Bの各値が0~1の範囲で格納されています(正確には学習が上手くいけば0~1の範囲になります)。この方法で学習してみます。
gf.PNG

 これでも学習自体は成り立っていいるのですが、なかなか鮮明な画像が出力されませんでした(学習する画像の枚数や、エポック数、学習率などの条件はすべて揃えています)。学習を重ねれば鮮明な画像が出力できるようになるのかもしれませんが、僕は待つことが嫌いなのでできるだけ早く学習させるようにしたいものです。ここで、これを解決するための「工夫」をすることにしました。

工夫その1:出力を入力と合成する

 見出しが早速答えとなってしまいましたが、この問題を解決する工夫として、「出力と入力を合成する」ということを思いつきました。これは、このAIの目標である「色を付ける」ということに関してとても合理的な手法だと思います。
 先ほど説明した学習の流れを変更し、誤差算出の前に「出力と入力を合成する」というステップを加えます。これは具体的に何をしているのかというと、「出力のR,B,Gの各値に入力の値をそれぞれ掛ける」という処理をしています。これは画像編集ソフトでレイヤー同士の合成の「乗算モード」と同じです。これにより、入力で色の暗い部分は0に近い値がかけられるため、合成後の画像は0に近い、つまり暗い色が出力されるようになります。これを用いれば、入力画像の明暗の情報を出力画像に継承することができます。この処理を踏まえ、先ほどの学習の流れを書き換えると、

 1. モデルに白黒画像を入力
 2. 出力画像と入力画像を合成
 3. 合成画像とカラー画像との誤差を求める
 4. 誤差をもとにモデルを更新する
 5. 1.に戻る

という流れになります。図で表すと次のようになります(画像はイメージです)。
キャプチャ.PNG
 これにより先ほどよりも圧倒的に早く成果を上げることができるようになります。こちらが結果です。
キャプチャ.PNG
 はっきりとした画像を作ることができました!顔の部分もしっかりと認識しています!と言いたいところですが、画像中央のみが着彩されています。これではわかりにくいかもしれないので、別の画像を用意しました。
キャプチャ.PNG
 二枚目の画像の人物を少し右にずらしてテストをしました。合成後の画像を見てもわかるように、人物の顔の右上は着色されず、画像中央のみが着彩されていることが分かると思います。これは使用したデータセットに画像中央に人の顔があるものが多いことが原因です。これではAIを使う意味がありません。初めから画面中央をオレンジ色に塗った画像を合成すれば済んでしまうからです。そこでこれを防ぐためにもう一つ「工夫」をすることにしました。

工夫その2:画像をランダムに切り抜く

 この問題の原因は画像中央に人の顔があることが原因です。そのため、画像中央から顔をずらすことでこの問題が解決できると考えました。ランダムなサイズの正方形でランダムな位置を切り抜けば顔の位置をずらすことができます。
agf.PNG

この作業を画像を読み込む際に行うことで、画面中央だけに着彩するということを防ぐことができました。(しかしシャツにも色を付けてしまっています。得意不得意があるのでしょうか。今後学習データを増やして再度挑戦します。)
キャプチャ.PNG
原理の説明は以上です。

実装

 ここからは実際にどのようなプログラムを書いたのかを解説します。

下準備

 プログラムを書く前に、画像データの入ったディレクトリを用意します。まずはD:\PythonPrograms\PaintFaceAI\TrainDataというフォルダを用意します。もちろん別の場所でも構いません。パスを変更した場合はプログラム内のディレクトリ指定用の変数を書き換えてください。このディレクトリには学習に使う訓練データを入れます。face (number).jpgとなるように画像を用意してください。(僕のパソコン(Windows)では画像を全選択→右クリック→名前を変更→faceと入力→Enterとする事で自動に番号付けをすることができました。)すべての画像のサイズは250×250です。また、最低でも8192枚の画像を用意してください(この数は後述の変数TRAIN_NUMの値です)。
キャプチャ.PNG
 同様にD:\PythonPrograms\PaintFaceAI\TestDataも用意します。TrainDataのディレクトリと同様の画像(ただし学習しないので白黒も可)をこちらは5枚用意します(もっと多くの画像をテストしたい場合はTEST_NUMの値を変更してください)。
キャプチャ.PNG
 最後に、モデルの保存先となるフォルダ(:D:\PythonPrograms\PaintFaceAI\Model)を用意してください。もちろんこの場所も自由に決めることができますが、その際は後述の変数のMODEL_LOCを変更してください。出力画像の保存先等はフォルダを自動で初期化するようにしたのですが、モデルは一度消えるとまた数時間学習しなければならないので、めんどくさいですが安全確保のため手動です。フォルダは空のままでOKです。

ライブラリのインポート

 使用するライブラリをインポートします。

Colorize.py
#ライブラリのインポート
import torch
from torch import nn
from torch import optim
import torch.nn.functional as F
import numpy as np
from PIL import Image
import os
import shutil

nnはニューラルネットワークを使うために、optimはPytorchが提供するオプティマイザーを使用するために、nn.functionalは誤差関数を使用するためにインポートします。尚、numpyは画像読み込み時に、osshutilは画像保存先等のディレクトリを整えるときに使います。

設定用の変数の定義

 良いパラメータを探す際や、様々な実験を行う際に、プログラムのあちこちに散らばっている設定を一つの箇所にまとめておけば楽なので、あらかじめ設定用の変数を用意し、プログラム内でそれらを使用します。

Colorize.py
#設定
SOURCE_IMAGE_SIZE = 250         #画像の縦と横のピクセル数
IMAGE_SCALE = 0.2               #元画像の何倍の大きさの画像を扱うのか
IMAGE_SIZE = int(SOURCE_IMAGE_SIZE*IMAGE_SCALE) #扱う画像のサイズ
TRAIN_NUM = 8192                #学習に使う画像の数
EPOCH_NUM = 16                  #同じデータセットを繰り返し学習する数
TEST_NUM = 5                    #テストする画像の数
LR = 0.0001                     #学習率
BATCH_SIZE = 128                #バッチサイズ
BASIC_COLOR_SPACE = "RGB"       #色空間の指定
#デバイスの指定(グラボが使えるなら使う)
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
LEARN = True    #学習を行うのか
TEST = True     #学習中に1エポックごとにテストを行うのか
TEST_VISUALIZE = True #テストで画像を出力するのかどうか
MODEL_NAME = "Model"#モデル保存時の名前

#使用するディレクトリのパス(適宜指定してください)

#学習用の画像が保存されたディレクトリ
TRAIN_DATA_LOC = r"D:\PythonPrograms\PaintFaceAI\TrainData"

#テスト用の画像が保存されたディレクトリ
TEST_DATA_LOC = r"D:\PythonPrograms\PaintFaceAI\TestData"

#最終的な出力画像の保存先
OUT_LOC = r"D:\PythonPrograms\PaintFaceAI\Output"

#入力画像の保存先
IN_LOC = r"D:\PythonPrograms\PaintFaceAI\Input"

#元画像(学習用の画像を切り抜き、リサイズ処理したもの)の保存先
SOURCE_LOC = r"D:\PythonPrograms\PaintFaceAI\Source"

#色情報(ネットワーク出力を入力と合成する前の画像)の保存先
COLOR_LOC = r"D:\PythonPrograms\PaintFaceAI\Color"

#モデルの保存先
MODEL_LOC = r"D:\PythonPrograms\PaintFaceAI\Model"

画像読み込み用クラス

 画像を読み込むための機能を持つクラスを用意します。
 このクラスはふたつのメソッドを持ちます。一つ目はload_batchメソッドです。これは学習時に使用します。指定された枚数の画像を読み込み、切り抜き、リサイズの処理を行った後にそれを白黒化します。また、カラー画像と白黒画像共に各ピクセルごとの色の情報を配列に格納し、それを返します。二つ目のload_test_imageでは、リサイズと白黒化のみを行い、指定枚数の白黒画像の情報の入った配列を返します。基本的にload_batchメソッドと同じです。

Colorize.py
#画像読み込み用クラス
class image_loder():
    #ミニバッチ学習に使用する1ミニバッチ分の画像を取得する
    def load_batch(self,image_number,batch_size,
        random_crop = True, crop_max = 200,message = ''):

        #画像のピクセル数と同じ数だけの要素を持つTensor型の配列を作る
        color_image = torch.Tensor(batch_size,3,IMAGE_SIZE,IMAGE_SIZE)
        greyscale_image = torch.Tensor(batch_size,1,IMAGE_SIZE,IMAGE_SIZE)

        #バッチサイズの回数繰り返す
        for num in range(batch_size):
            #ファイル名
            image_name = "face (" + str(int(image_number+1+num)) + ").jpg"

            #画像読み込みのついでに進捗を確認できるようなメッセージを出力
            print("Loading Image :",image_name,"  |  ",message)

            #画像を読み込む
            image = Image.open(TRAIN_DATA_LOC + "\\" + image_name)

            #ランダムな場所で切り抜く
            if random_crop == True:
                #切り抜く幅の設定
                crop_size = np.random.randint(1,crop_max)
                #切りぬく正方形の左上の座標の指定
                x = np.random.randint(0,crop_size)
                y = np.random.randint(0,crop_size)
                #切り抜く
                image = image.crop((x,y,x+SOURCE_IMAGE_SIZE-crop_size,y+SOURCE_IMAGE_SIZE-crop_size))

            #指定の大きさにリサイズ
            image = image.resize((IMAGE_SIZE,IMAGE_SIZE)).convert(BASIC_COLOR_SPACE)

            #配列の各要素に読み込んだ画像の各ピクセルの色を格納する
            for x in range(IMAGE_SIZE):
                for y in range(IMAGE_SIZE):
                    pixcel = image.getpixel((x,y))
                    #白黒なのでRGBの平均を取る。その後0~1の範囲に変換する
                    color_image[num,0,x,y]=pixcel[0]/255
                    color_image[num,1,x,y]=pixcel[1]/255
                    color_image[num,2,x,y]=pixcel[2]/255

            #白黒に変換
            image = image.convert('L')

            #配列の各要素に読み込んだ画像の各ピクセルの色を格納する
            for x in range(IMAGE_SIZE):
                for y in range(IMAGE_SIZE):
                    pixcel = image.getpixel((x,y))
                    greyscale_image[num,0,x,y]=pixcel/255

        #配列の形状を整える
        color_image = color_image.view(batch_size,
                                        3,
                                        IMAGE_SIZE,
                                        IMAGE_SIZE).to(DEVICE)
        greyscale_image = greyscale_image.view(batch_size,
                                        1,
                                        IMAGE_SIZE,
                                        IMAGE_SIZE).to(DEVICE)

        #画像データの配列を返す
        return [color_image,greyscale_image]

    #テスト用に使用する画像の取得。取得枚数は別指定。
    #仕組みはほぼload_batchと同じなのでコメントは省略
    def load_test_image(self,test_num,message = ''):
        greyscale_image = torch.Tensor(test_num,1,IMAGE_SIZE,IMAGE_SIZE)
        for num in range(test_num):
            image_name = "face (" + str(int(num+1)) + ").jpg"
            print("Loading Image :",image_name,"  |  ",message)
            image = Image.open(TEST_DATA_LOC + "\\" + image_name)
            image = image.resize((IMAGE_SIZE,IMAGE_SIZE)).convert(BASIC_COLOR_SPACE)
            image = image.convert('L')
            for x in range(IMAGE_SIZE):
                for y in range(IMAGE_SIZE):
                    pixcel = image.getpixel((x,y))
                    greyscale_image[num,0,x,y]=pixcel/255
        greyscale_image = greyscale_image.view(test_num,
                                        1,
                                        IMAGE_SIZE,
                                        IMAGE_SIZE).to(DEVICE)
        return greyscale_image

load_batchメソッドの引数について

引数が多いので簡単に説明します。

引数 説明
image_number 画像読み込み時に、何番目の画像から読み込むのかを指定します。
batch_size バッチサイズを指定します。このメソッドではバッチサイズ個の画像を返します
random_crop ランダムな位置で切り抜くのか否かを指定します。ランダムに切り抜くことで画像中央ばかりに人の顔があることがなくなります
crop_max 切り抜く幅の最大値を指定します。例えばcrop_maxが10で、元画像のサイズが50×50の場合、40×40~50×50の大きさの画像が作られます
message コンソールに表示するメッセージを指定します。画像読み込みが一番時間がかかるため、このついでに進捗を確認できるので便利です。必ず必要というわけではありません。

load_test_imageメソッドの引数について

こちらも簡単に説明します。

引数 説明
test_num 何枚の画像をテストするのかを指定します。
messege コンソールに表示するメッセージを指定します。

ニューラルネットワークを構築するクラス

 今回はnn.Moduleクラスを継承するという形でネットワークを定義します。

Colorize.py
#ニューラルネットワークの設定
class network(nn.Module):
    #nn.Moduleを継承してモデルを用意する
    def __init__(self):
        super(network,self).__init__()

        #畳み込み層(画像を圧縮して学習速度が高まった)
        self.features = nn.Sequential(
            nn.Conv2d(1,8,kernel_size=2,stride=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2,stride=1),
        )

        #全結合層
        self.classifier = nn.Sequential(
            nn.Linear(18432,(IMAGE_SIZE**2)*3),
            nn.ReLU(inplace=True),
            nn.Linear((IMAGE_SIZE**2)*3,(IMAGE_SIZE**2)*3)
        )

    #インプットからアウトプットまでの流れ
    def forward(self,x):
        x = self.features(x)
        x = x.view(x.size(0),-1)
        x = self.classifier(x)
        return x

可視化用のクラス

 プログラム内での画像データは配列になっているので、これを可視化する必要があります。そのためのクラスを作ります。

Colorize.py
#可視化用のクラス
class visualizer():
    def __init__(self):
        return None

    #画像に変換
    def visualize(self,image,size,name,show_or_not,color_space):
        #画像の縦横のピクセル数を用意
        width,height = size

        #指定サイズの画像を用意
        image_v = Image.new(color_space,(width,height))

        #白黒画像でない場合
        if color_space != "L":
            for x in range(width):
                for y in range(height):
                    #各ピクセルの色を入力された配列を参照して決定する。配列では0~1だが、0~255に変換してから出力する
                    value=image[:,x,y]
                    image_v.putpixel((x,y),(int((value[0])*255),int((value[1])*255),int((value[2])*255)))

        #白黒画像の場合
        else:
            for x in range(width):
                for y in range(height):
                    #各ピクセルの色を入力された配列を参照して決定する。配列では0~1だが、0~255に変換してから出力する
                    value=image[:,x,y]
                    image_v.putpixel((x,y),(int(value[0]*255)))

        #画像の表示
        if show_or_not == True:
            image_v.show()

        #画像の保存
        image_v.convert("RGB").save(name+".bmp")

着彩用のクラス

 このプログラムの本体となるクラスです。かなりややこしく仕上がっております。今回はメソッドごとに説明します。

__init__メソッド

 このクラスの初期設定です。ここではモデルの作成と最適化方法の選択をします。今回は最適化方法はAdamを使用しました。

Colorize.py
#色を塗るAIのクラス
class painter():
    def __init__(self):
        #ネットワークの作成  
        self.model = network().to(DEVICE)
        print("Created Model:\n",self.model) 

        #最適化手法の設定
        self.optimizer = optim.Adam(self.model.parameters(),lr = LR)

        #続く...

multiplyメソッド(合成処理)

 先に合成処理用のメソッドを作っておきます。これを一つのメソッドにする必要はないかもしれませんが、合成法を変える場合に便利なのでメソッドにしました。

Colorize.py
    #白黒画像とネットワークの出力を合成
    def multiply(self,a,b):
        return a * b
        #続く...

paint_trainメソッド

 このメソッドでは学習時の画像処理関連の流れをまとめています。白黒画像を受け取ると、それをネットワークに入力し、出力を得ます。さらにその出力を入力と合成し、合成されたものを返します。また、学習の進捗を確認するために画像を可視化し保存しています。

Colorize.py
    #学習時の画像処理の担当
    def paint_train(self,epoch,num,color_image,greyscale_image,visualize = True):

        #白黒画像をニューラルネットワークに入れて計算させる
        output = self.model(greyscale_image).view(BATCH_SIZE,3,IMAGE_SIZE,IMAGE_SIZE).to(DEVICE)

        #ニューラルネットワークの出力をcolor_dataとして保存
        color_data = output

        #画像を合成
        output = self.multiply(output,greyscale_image)

        #バッチごとに画像を保存する(とりあえず配列の0番目の画像を出力する)
        if visualize == True:
            print("Visualizing Source Image")
            vi.visualize(color_image[0].view(3,int(IMAGE_SIZE),int(IMAGE_SIZE)),
                        [int(IMAGE_SIZE),int(IMAGE_SIZE)],
                        SOURCE_LOC + "\source_" + str(epoch+1) + "-" + str(num),
                        False,BASIC_COLOR_SPACE)

            print("Visualizing Input Image")
            vi.visualize(greyscale_image[0].view(1,int(IMAGE_SIZE),int(IMAGE_SIZE)),
                        [int(IMAGE_SIZE),int(IMAGE_SIZE)],
                        IN_LOC + "\imput_" + str(epoch+1) + "-" + str(num),
                        False,"L")

            print("Visualizing Color Data")
            vi.visualize(color_data[0].view(3,IMAGE_SIZE,IMAGE_SIZE),
                        [IMAGE_SIZE,IMAGE_SIZE],
                        COLOR_LOC + "\color_" + str(epoch+1) + "-" + str(num),
                        False,BASIC_COLOR_SPACE)

            print("Visualizing Output Image")   
            vi.visualize(output[0].view(3,IMAGE_SIZE,IMAGE_SIZE),
                        [IMAGE_SIZE,IMAGE_SIZE],
                        OUT_LOC + "\output_" + str(epoch+1) + "-" + str(num),
                        False,BASIC_COLOR_SPACE)

        return output
        #続く...

updateメソッド

 モデルの更新をします。引数に本来のカラー画像(正解データのようなもの)とAIの出力を持ち、これらの誤差を計算したのちに誤差伝搬します。

Colorize.py
    #モデルの更新
    def update(self,color_image,output):
        print("Updating Model")

        #誤差を計算する(誤差関数)
        loss = F.smooth_l1_loss(color_image,output)

        #誤差関数に依存しない誤差の計算(差の合計)
        real_loss = torch.abs(color_image-output).sum()

        #誤差伝搬
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

        print('Loss:',str(float(real_loss)))
        #続く...

trainメソッド

 このメソッドでは、学習全体の流れを記述します。

Colorize.py
    #学習
    def train(self):
        print("Train")

        for epoch in range(EPOCH_NUM):
            #テストするならテストする
            if TEST:
                print("Test")
                self.test(epoch)

            #学習本体
            for num in range(int(TRAIN_NUM/BATCH_SIZE)):
                #進捗確認用のメッセージ
                progress = "epoch : "+str(epoch+1)+" - batch : "+str(num+1)+" / "+str(int(TRAIN_NUM/BATCH_SIZE)) + " - Batch size = "+str(BATCH_SIZE)+" - lr = "+str(LR)
                print("\n",progress)

                #テストデータを着彩#num番目の画像を読み込む
                [color_image,greyscale_image] = il.load_batch(num*BATCH_SIZE,BATCH_SIZE,message=progress,random_crop = True)

                #画像処理
                output = self.paint_train(epoch,num,color_image,greyscale_image,visualize = False)

                #モデルの更新
                self.update(color_image,output)

            print("Saving Model")
            #モデルの保存
            torch.save(self.model.state_dict(),MODEL_LOC+"/"+MODEL_NAME+str(epoch)+".pth")
            #続く...

paint_testメソッド

 テスト時の画像処理を担当します。基本的にpaint_trainメソッドと同じです。

Colorize.py
    #テストでの画像処理を担当(基本的にpaint_trainと同じ。
    #paint_testでは出力する画像の枚数を指定できるようにした。詳細なコメントは省略)
    def paint_test(self,epoch,greyscale_image,visualize = True,visualize_num = 1):
        output = self.model(greyscale_image).view(TEST_NUM,3,IMAGE_SIZE,IMAGE_SIZE).to(DEVICE)
        color = output
        output = self.multiply(output,greyscale_image)
        if visualize == True:
            for v_num in range(visualize_num):
                print("Visualizing Input Image")
                vi.visualize(greyscale_image[v_num].view(1,int(IMAGE_SIZE),int(IMAGE_SIZE)),
                            [int(IMAGE_SIZE),int(IMAGE_SIZE)],
                            IN_LOC + "\Test_imput_" + str(epoch) + "-" +str(v_num+1),
                            False,"L")
                print("Visualizing Color Data")
                vi.visualize(color[v_num].view(3,IMAGE_SIZE,IMAGE_SIZE),
                            [IMAGE_SIZE,IMAGE_SIZE],
                            COLOR_LOC + "\Test_color_" + str(epoch) + "-" +str(v_num+1),
                            False,BASIC_COLOR_SPACE)  
                print("Visualizing Output Image")   
                vi.visualize(output[v_num].view(3,IMAGE_SIZE,IMAGE_SIZE),
                            [IMAGE_SIZE,IMAGE_SIZE],
                            OUT_LOC + "\Test_output_" + str(epoch) + "-" +str(v_num+1),
                            False,BASIC_COLOR_SPACE)
        return output
        #続く...

testメソッド

 テスト時の全体の流れを記述します。これもtrainメソッドとほぼ同じですが、誤差の計算やモデルの更新はしません。

Colorize.py
    #テスト
    def test(self,text):
        print("TestStart")

        #テストデータを着彩#num番目の画像を読み込む
        greyscale_image = il.load_test_image(TEST_NUM)

        self.paint_test(text,greyscale_image,visualize = TEST_VISUALIZE,visualize_num = TEST_NUM)

        print('Test End')

ディレクトリを初期化するメソッド

 画像の出力先のフォルダをいったん削除し、再びフォルダを作ります。手動でフォルダ内の画像を削除する手間が省けるのででスムーズに実験が進みます。

Colorize.py
#画像保存用のディレクトリを用意する
def directory_setup():
    #フォルダがない状態でフォルダを削除しようとするとエラーになるのでエラーになったら削除しない
    try:
        #出力先のフォルダの消去
        shutil.rmtree(SOURCE_LOC)
    except:
        pass
    try:
        #出力先のフォルダの消去
        shutil.rmtree(IN_LOC)
    except:
        pass
    try:
        #出力先のフォルダの消去
        shutil.rmtree(OUT_LOC)
    except:
        pass
    try:
        #出力先のフォルダの消去
        shutil.rmtree(COLOR_LOC)
    except:
        pass

    os.mkdir(SOURCE_LOC)
    os.mkdir(IN_LOC)
    os.mkdir(OUT_LOC)
    os.mkdir(COLOR_LOC)

実行

 最後に作成したクラスのオブジェクトを作成し、学習を開始するコードを書きます。

Colorize.py
#クラスの作成
il = image_loder()
p = painter()
vi = visualizer()

#ディレクトリを整理
directory_setup()

#学習
if LEARN:
    p.train()

p.test("Result")

print("Finish")

これを実行すると、画像保存用のフォルダが作られ、学習が進みます。気長に待ちましょう。ちなみに実行画面はこんな感じで、進捗度合いなどが表示されます。
キャプチャ.PNG
実装の説明は以上です。

おわりに

 今回作成したAIの最大の成果は、ほとんどの画像でシャツの白い部分などを塗らないことを学習したことだと考えています。背景色まで肌色に塗ってしまっていることに関しては、背景色を適切に塗るには膨大な画像データが必要となるため解決は難しいと考えています。しかし、瞳の色や唇の色など、人間の顔である程度共通する箇所は適切な色を塗れるようにしたいため、高解像度の画像を扱うようにする、データの量を増やすなどして改善していきたいと考えています。

主な参考文献(2019/04/14時点)

  1. PyTorchニューラルネットワーク実装ハンドブック(書籍)
  2. torch.Tensorの作成と基本操作
  3. PIL/Pillow チートシート
  4. 初めてのPython画像処理
  5. Python, Pillowで画像の一部をトリミング(切り出し/切り抜き)
  6. Python, Pillowで画像を一括リサイズ(拡大・縮小)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

#python で 複数の #JSON 配列をフラットに結合する簡単なスクリプトの例

script

#!/usr/bin/env python3

import sys, json

results = []

for input_data in sys.argv[1:]:
  results += json.loads(input_data)

print(json.dumps(results))

exe

$ ./concat-array.py '[1,2,3]' '[{"a":"b","c":"d"}]' '["e","f"]' | jq .
[
  1,
  2,
  3,
  {
    "a": "b",
    "c": "d"
  },
  "e",
  "f"
]

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/1234

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

コミケ・技術書典で同人誌を作った時の小技をまとめる

はじめに

この記事ではAdobe Acrobatを中心とした同人誌作成のTipsを紹介します。
4/14に開催の技術書典06に出品するので、そのとき使った細かい小技を紹介します。
技術書典やコミケなど、同人誌を作る機会が多いかと思われますので、その助力となれば幸いです。

ついでに技術書典06の宣伝(大事)

環境

  • OS: Windows10
  • Adobe Acrobat Pro DC 2019.010.20099
    • Adobe Creative cloudのAcrobatの年間プラン(毎月1700円のプラン)

1. Acrobatでページ番号を振る

同人誌の印刷にはノンブル(ページ番号)を通しで振る必要があります。
Acrobatでノンブルを追加するにはpdfを編集画面でヘッダーとフッター>追加で設定できます。

image.png

そして、追加をクリックして出てきたダイアログにて中央フッターテキストにカーソルを合わせて、ページ番号を挿入をクリックすれば、ページ番号が付きます。

image.png

  • before

image.png

  • after

image.png

また、左右のヘッダーにつける場合は(左綴じを仮定して)まず右ヘッダテキストページ番号を挿入してから、ページ範囲オプションをクリック、出てきたダイアログのサブセット項目を奇数ページのみに変更し、OKボタンを押します。

image.png

そして、ヘッダーとフッターと追加ダイアログでOKボタンを押せば、
元のpdfの奇数ページにページ番号が付いてることがわかります。

image.png

同様に、偶数ページの左ヘッダにページ番号を入れれば、開いたときの左右ページにページ番号が付くようになります。

image.png

(縮小してトリミングしたからモアレになってる…!)

2. Acrobatで画像オブジェクトを白黒にする

印刷所にオンデマンド印刷本文モノクロ(技術書の場合これが多いと思う)の依頼をする上では、入稿するデータは印刷のミスを防ぐためグレースケールにします。

Adobe Acrobat proでグレースケールにするためには印刷工程メニューを開き、色を置換をクリックします。

image.png

そして、開いた色置換ダイアログに対して、変換プロファイルをDot Gain 15%, 変換オプションを黒を維持とします。

image.png

これでOKを押して変換すると以下のようになります。

  • before

image.png

  • after

image.png

pdfを画像に変換する

boothのサンプルやpixiv、twitterに宣伝、作品紹介ポップを作成するときにpdfの画像データが欲しくなります。
ここで、pdfの1ページ1ページを画像編集する方法を記載します。
Acrobat proでもできますが、時間がかかるので(1枚およそ30秒くらいかかった)、追い込み時期の早く寝ないと次の日支障が出るというときには不便です。

ここでpython3でpdfを画像に変換しましょう。
python3はAnacondaなどでインストールすればいいです。

https://haitenaipants.hatenablog.com/entry/2018/07/17/000101
を参考にします。

from pdf2image import convert_from_path
import glob
import os

input_pdf_path = '画像化したい本のパス'
dir_name = '出力ディレクトリ名'
book_name = '画像化したい本の名前'
image_ext = 'png'
save_ext = 'png' # jpgで出力したいときはjpeg

# pdf画像変換処理
images = convert_from_path(input_pdf_path)

for id, image in enumerate(images):
    output_img_path = os.path.join(dir_name, '{0}_{1:03}.{2}'.format(book_name, id, image_ext))
    image.save(output_img_path, save_ext)

これで何十分もかけずにpdfを画像化が可能です。

ちなみにtimeモジュールで時間を測ると8.8377秒でした。

import time
from contextlib import contextmanager

@contextmanager
def time_estimate():
    start_time = time.time()
    yield
    end_time = time.time()
    print(end_time-start_time)

# [略]

with time_estimate():
    # pdf画像変換処理
    # [略]

宣伝

折角だし技術書典の宣伝です(前日に宣伝するのか…)

私、いりすは技術書典06 サークルスペースお06 「不思議の国の入巣次元」にてサークル参加いたします。
新刊はchatbot自作、AR chatアプリ+AIの実装、ラズパイリモコンハックを収録した合同誌となっております。

おしながき: https://twitter.com/irisuinwl/status/1116956010088808448

おまけ. 同人イベント参加のノウハウ

ついでにコミケに参加した時+技術書典で新刊作成のときのノウハウを記載しておきます。
ぽえむみたいなものですが、同人イベントに参加してみたい人の参考になればと思います。
技術書典が終わったら追記するかも。

以前、自分のブログに書きましたが、感情的に書いており、ノイズが多いので役に立ちそうな部分だけピックアップします。
参考: https://irisuinwl.hatenablog.com/entry/2019/01/09/053939

  • 同人誌作成で工夫したこと

    • 原稿はtexだったのでgit,Bitbucketで管理すると一日にどれだけ作業できるか見えて、スコープの調整などがしやすかった。
    • コミケの時はBitbucket, 技術書典ではgitのプライベートリポジトリを利用した。
    • 進捗管理はtrelloを使った。
    • 合同誌作成の際のコミュニケーションツールはslackを使ったら円滑にコミュニケーションできた。
    • 何を作りたいか、読んだ人に何を伝えたいかをmarkdownに纏めて、何を作るべきかを明確にした(個人用のインセプションデッキや企画書みたいなもの)
      • 個人的にはこれが一番重要だと思う。
  • 会計

    • B5,92P 印刷部数30部
    • 収入: 27000円 (完売)
    • 支出: 97000円(絵師さんへの依頼料含む)
    • [反省] 30部しか刷ってないので利益は出ないし、売り切れて現場で手に取ってもらえなかった。
      次回のイベントに参加することを考えたら黒字になるくらいは刷ればよかった。
  • 宣伝とサークルスペース

    • 宣伝はpixivとtwitter
      • pixivを見て来ました! twitterを見て来ました! って言って頂けたので効果はあったと実感
    • スペースの設営に使ったのは以下 ( こんな感じ: https://twitter.com/irisuinwl/status/1079527387040108544 )
      • 見本を立てる小型のイーゼル(100均で売ってる)
      • ポスター立て
      • スペース用テーブルクロス(あの布という製品を使いました)
      • テーブルクロス前面に貼るポスター
      • 頒布物が分かるポップ
  • その他

    • イベント初参加で売り子の一人が体調不良、もう一人が寝坊して最初一人での対応となったので、何が起きても楽しめるように覚悟を決めよう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ターミナルとしてFish Shellを使う際のPATH設定

背景

MacでPython3系をインストールした際に、呼び出すにはpythonではなくpython3コマンドを打たないといけない事にじれったさを感じた

環境

  • MacOS Mojave
  • Fish Shell 3.0.2
  • Homebrew 2.1.0

解決方法

  • bashと同様にPATHを設定すれば良い

手順

  • brew info pythonでpython3のパスを探す
    • 私の場合は/usr/local/opt/python/libexec/binでした
  • ~/.config/fish/config.fishを開く(無い場合は新規作成する)
  • set PATH /usr/local/opt/python/libexec/bin /usr/local/bin /usr/sbin $PATH と追記する

1の参考:https://codeday.me/jp/qa/20190403/510812.html
2の参考:https://qiita.com/ledsun/items/8ca1a450b21c8ebc9670

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

DeepLabに代わり現在のSOTAであるFastFCN(JPU)の論文解説

2019/3/28に投稿された、今現在のセグメンテーションのstate-of-the-artです
Joint Pyramid Upsampling (JPU)という手法を用いて、state-of-the-artの精度かつ、DilatedFCNに比べて3倍以上高速なセグメンテーションを可能にした論文です

論文の概要

論文名: FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic
arXiv: https://arxiv.org/abs/1903.11816
Github: https://github.com/wuhuikai/FastFCN

Abstract

  • 最近のセグメンテーションのアプローチでは、高解像度の特徴量マップを得るためにDilated Convolutionをbackbornに使っているが、これは計算コストとメモリ消費が激しい
  • これを解決するために、Joint Pyramid Upsampling (JPU)というモジュールを導入します これは高解像度の特徴量マップを得る問題を、Joint Upsampleの問題に置き換えるものです。
  • JPUによって、精度を落とすことなく、計算コストを3倍以上減らすことができます
  • Dilated ConvolutionをJPUモジュールに置き換えることによって、Pascal Context datasetADE20K datasetにおいて、3倍以上高速に動作しながら、state-of-the-artのスコアを出しました。

1. Introduction

  • 最初のFCN(Fully Convolution Network)は、ダウンサンプルによって低解像度の特徴量マップを生み出すため、細かい情報は失われ、境界線付近で間違った予測をすることが多くなります。Fig 1(a)のように、典型的なFCNでは5回ダウンサンプリングをし、特徴量マップは約1/32の解像度になる。

  • 高解像度の特徴量マップを得るため、元のFCNをセマンティックな情報を得るためのEncoderとして用いて、DecoderではEncoderの中の特徴量マップを徐々に取り入れていくことで、徐々に空間情報を補填していくEncoderDecoder構造が提案されています(Fig 1(b))

  • その後提案されたDeeplabでは、最初のFCNから最後の二つのダウンサンプルを取り除き、代わりにdilated (atrous) convolution を追加して、受容野の大きさが変わらないようにしています。この種の方法はEncoderDecoder構造のモデルを様々なベンチマークで上回っていきました。Fig 1(c)でわかるように、DilatedFCNの最後の特徴量マップは他のものより4倍程度大きく、より多くの空間情報を保持しています。

image.png

  • dialted convolutionは最後の特徴量マップがより高解像度の空間情報を保持するために重要な役割を果たしていますが、計算コストとメモリ使用量が大きい点が問題になっています。

  • この問題を解決するためにJoint Pyramid Upsampling (JPU)を導入します。最初のFCNをbackbornとして用いて、その各層から得られる特徴量マップをJPUに通すことで、最終的にOutput Strideが8(入力画像の1/8の解像度)の高解像度の特徴量マップが得ることができます。さらに、DilatedFCNに比べて精度を落とさないまま、計算コストとメモリ使用量も大幅に減らすことができます

  • 実験をすると、3倍早く動きながらstate-of-the-artの精度を出すことができた

    • Pascal Context datasetでは53.13%(mIoU)でSOTA
    • ADE20K datasetでは42.75%(mIoU)でSOTA

2. Related Work

2.1. Semantic Segmentation

EncoderDecoderとDilatedFCNについてIntroductionとほぼ同じことを説明してるだけなので割愛

2.2. Upsampling

低解像度の特徴量マップをupsampleして高解像度のものを取り出す必要があります

Joint Upsampling

この内容は3.3.1 Backgroundの方で詳しく説明するのでここでは割愛

Data-Dependent Upsampling

ラベル空間の冗長性を利用して低解像度の特徴量マップからpixel-wiseの予測をするDUpsamplingもこの論文の方法と関連していますが、DUpsamplingはラベル空間に大きく依存しており、複雑なラベルには対応できなくなる点がこの論文の優位性

3. Method

3.1. DilatedFCN

これもほぼIntroductionと同じ内容なので割愛。内容がないよう〜
DeepLabではFig 1(c)にあるように最後の2層はDilated Convolutionにして受容野が変わらないようにしますよっていうお話

ちょっとだけ参考として、dilated(atrous) Convolutionというのはこんな感じです。gif画像はDilation rate(畳み込みをする間隔)が2の場合で、1の場合は普通の畳み込みと同じになります。
dilated畳み込み

3.2. The Framework of Our Method

image.png
この論文では、計算コストとメモリ使用量を抑えつつ、DilatedFCNの最終層の特徴量マップを近似することが目的

  1. そのためにまず、Dilated FCNで無くした最後二つの普通のConvolutionを元に戻します。これによって、Fig 2にあるようにbackbornは元のFCNと同じものになります
  2. そして、Dilated FCNと似た特徴量マップを得るため、最後の3つの層の特徴量マップをインプットとするJoint Pyramid Upsampling (JPU)を導入します。
  3. そのあとmulti-scale context module(PSPもしくはASPP)かglobal context module(Encoding)に通して、最終的な予測値を出します。

backbornにResnet101を使った場合、Dilated Convolutionに比べて、residual blockが23個の場合は1/4の計算コスト&メモリ使用量、3個の場合は1/16になります

3.3. Joint Pyramid Upsampling

Dilated FCNと同じ特徴量マップを作り出すのは、Joint Upsampleの問題として考えることができる

3.3.1 Background

Joint Upsampling

低解像度のguidance画像$x_l$から、低解像度のtarget画像$y_l$が、

y_l = f(x_l)

という変換で得られるとします。これから考える問題は、この$f(\cdot)$を近似する、$\hat{f}(\cdot)$を求めることです。
一般的に、$\hat{f}(\cdot)$は$f(\cdot)$よりも計算コストが小さくなります。例えば、$f(\cdot)$がもし多層パーセプトロンで形成されたものなら、$\hat{f}(\cdot)$は簡単な線形演算子の形で求められます。
この$\hat{f}(\cdot)$を用いることで、高解像度のguidance画像$x_h$が与えられた時は、

y_h = \hat{f}(x_h)

として、少ない計算量で高解像度のtarget画像を得ることができます。

問題を定式化すると、

y_h = \hat{f}(x_h), \mathrm{where}~ \hat{f}(\cdot) = \mathrm{argmin}_{h(\cdot)\in \bf{H}} \| y_l - h(xl) \| 

ここで$\bf{H}$は考えられる変換、$||\cdot||$は距離関数です

Dilated Convolution

Dilated Convolutionは、Deeplabで提案された、受容野の大きさを変えずに高解像度の特徴量マップを取り出す方法ですが、これは次のように考えることができます。
Fig 3(a)は、一次元でdilation rateが2の場合を示していますが、この操作は以下の3つの操作に分解できます

  1. Split : inputの$f_{in}$を、$f_{in}^0$と$f_{in}^1$の二つのグループにわける
  2. Conv : それぞれに同じ畳み込みを施し、$f_{out}^0$と$f_{out}^1$を得る
  3. Merge : $f_{out}^0$と$f_{out}^1$を互い違いになるように統合して、$f_{out}$を得る

ここでは簡単のために一次元で考えていますが、2次元でも同様の分解ができます。
image.png

Stride Convolution

Stride Convolutionは、inputの特徴量を、解像度を減らしてoutputの特徴量に変換する方法です。
Fig 3(b)で一次元の場合を示していますが、これも二段階の操作に分解できます

  1. Conv : $f_{in}$に普通の畳み込みをして、中間の特徴量$f_m$を取り出す
  2. Reduce : $f_m$の奇数番目を取り除いて、$f_{out}$を得る

これも2次元の場合でも同様のことが成り立ちます。

3.3.2 Reformulating into Joint Upsampling

DilatedFCNと、提案手法の違いを見ていきます。
例として、backbornのConv4の特徴量マップ(Fig 2のConv4の出力)をinputとして、そこからoutputの特徴量マップを作る方法で比較します。

まず、DilatedFCNでは、まず最初にinputの特徴量マップ$x$に普通の畳み込みをしたあと、dilated畳み込みを何度か行なって、outputの特徴量マップ$y_d$を作ります。
これは以下のように定式化できます。

  • [記号の説明]
    • $C_r$ : 普通の(regular)畳み込み($C_r^n$は$C_r$が$n$層あることを示す)
    • $C_d$ : dilated畳み込み
    • $C_s$ : stride畳み込み
    • $S$ : 上で説明したSplitの操作
    • $M$ : 上で説明したMergeの操作
    • $R$ : 上で説明したReduceの操作
    • $\rightarrow$ は操作を施すことを意味します

image.png

一行目と二行目の式変形は、先ほど説明したようにdilated畳み込みの分解で、三行目に行くときに、$S$と$M$の操作は相殺されます。
四行目の$y_m$は、$x$に$C_r$を施したものです。
五行目の$y_m^0$と$y_m^1$は、$y_m$を偶数番目と奇数番目で分解したものです

 一方、提案手法ではoutput特徴量マップ$y_s$は以下のようにして得られます。

image.png

以上より、$y_d$と$y_s$は、同じ$C_r^n$を$y_m$と$y_m^0$のどちらに施すかという点だけがことなり、$y_m^0$は$y_m$のダウンサンプリングとなっています。
そのため、$x$と$y_s$が与えられたとき、$y_d$を近似する$y$は、以下のようにして求めることができます。
image.png

これは先ほど定式化したJoint Upsamplingの問題そのものです。
Conv5をインプットとした場合にも同じ結果が得られます。
これから、この$\hat{h}$を表現する方法を考えていきます。

3.3.3 Solving with CNNs

$\hat{h}$をCNNで表現する方法を考えます。
必要な要素としては、

  • $x$に$C_r$を施して、$y_m$を作る
  • そして、$y_m^0$からの特徴量マップと、$y_s$を一つに集める

ことが挙げられます。

そのために、以下のようにJPU moduleを定義しました。

image.png

具体的には、

  1. まず、Fig 4(a)の部分で、inputの特徴量マップに普通の畳み込みをします。これは$y_m$を作り出すことに対応してます
  2. 得られたそれぞれの特徴量マップの次元を減らします。これにより計算コストを抑えることができます。
  3. 特徴量マップをupsampleして、cancatして$y_c$をつくります。
  4. Fig 4(b)の部分で、$y_c$に、dilation rateが$1,2,4,8$のdilated畳み込みを並行して行います。

dilation rate=1の操作は、Fig 5の青で囲んだ部分をたたみ込んでいるので、&y_m^0$と$y_m$のそれ以外の部分との関係性を得る働きをします。
dilation rateが2, 4, 8の操作は、Fig 5の緑で囲んだ部分(の一部)をたたみ込むことになるので、求めたい$y_m^0$から$y_s$への写像$\hat{h}(\cdot)$を形成する働きを持ちます。

image.png

このようにして、JPUはマルチスケールの特徴量を、マルチレベルで取り込むことが可能です。これは最終層の特徴量しか考慮しないASPP moduleとの大きな違いです。

そして最後に、$y_m^0$と$y_m$のそれ以外の部分をつなぐために、Fig 4(c)の部分でもう一度普通の畳み込みを行います。これの出力が最終的な予測値となります。

4. Experiment

4.1. Experimental Settings

Dataset

Pascal Context datasetの60ラベルを使いましたという話

Implementation Details

  • PyTorchで実装
  • 前処理はrandom scaleとrandom fliplr
  • そのあと480×480にクロップして、batchsize=16で学習
  • optimizerはSGDでmomentum=0.9, weight_decay=1e-4、80epoch学習
  • 学習率は最初0.001から徐々に減らしていく
  • loss funcは、pixel-wise cross-entropy lossを使う
  • ResNet-50とResNet-101をbackbornとして使う

ここからは実験の結果だけを簡単に示していきます

Table 1

Headにつかうモジュールと、output stride(OS)を変えたときに精度はどうなるか
JPUが最も良い結果となっている
image.png

Figure 6

様々なUpsamplingのモジュールを使った場合の予測値
image.png

Table 2

計算時間がどれくらいかかるか(FPS)を調べたもの
実行速度はFPNと同じくらいだが、JPUの方が精度は優っている
image.png

Table 3

Pascal Context dataset(val)で、歴代のSOTA手法に比べたJPUの精度
State of the art‼️
image.png

Table 4

ADE20K(val)での精度
これもJPUが一番
image.png

Table 5

ADE20K(test)での精度
これもJPUが一番
上の二つはCOCO-Place challenge 2017の一位と二位
image.png

Figure 7

視覚的な結果
image.png

5. Conclusion

  • dilated convolutionとstride convolutionの違いを解析し、その解析を元に、高解像度の特徴量マップを生成するJPU moduleを提案した。
  • dilated convolutionをJPUに変更することにより、精度はそのままで計算コストを3倍以上抑えることができた。
  • 実験の結果、JPUは他のupsampleモジュールよりも高精度であることがわかった
  • 二つのセグメンテーションのデータセットで、JPUはstate-of-the-artの成績を収めることができた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【自己学習】Pythonで開発するための環境構築

始めに

本記事は自己学習の結果を記事として残すためのものであり、自分と同じくこれから機械学習の勉強を始めようとしている方の、若干の手助けとなればと思い書いています。
Qiitaにはこの手の記事がたくさん存在しますが、「ここは分かってるだろうから説明は省くね」パターンが多く、自分には説明が足りない記事が多いため、バカ(自分)にも分かるように細かく書いているつもりです。
本当は実装部分まで書こうと思ったんですが、環境構築部分のボリュームが思いの外多かったため、分割することにしました。
※理解が足りてなく、間違いがある可能性があるので、ご指摘をいただけたら嬉しいです。

内容

  • Pythonの開発環境の構築

単語

  • Homebrew
  • pyenv
  • pyenv-virtualenv

やったこと

機械学習の勉強を始めたとき、pythonの初心者でもあったので、環境を用意することに物凄く手間取りました。まずはPythonで開発するために必要な環境構築に関して記載しています。


環境の準備

1. Homebrew

1.1 Homebrewとは

Homebrewとは、MacOSのパッケージ管理ツールだそうです。もちろん、Homebrewを使用せずとも、インストーラーをダウンロードし、展開することで、パッケージのインストールは可能です。ただ、Homebrewを使用することで、ターミナルからコマンドを打つだけで、目的のパッケージのインストール/アンインストールが可能となり、手間が省けます。ここでは、Homebrewを使用して、後述するpyenvとpyenv-virtualenvをインストールします。

1.2 Homebrewをインストール

以下のコマンドをターミナル上で叩くだけです。

 /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

※上記コマンドは頻繁に更新されるそうなので、以下HPにアクセスして、最新のコマンドを取得することをお勧めします。
HomebrewのHP

つらつらと大量のログが表示され、最後にユーザのパスワードを要求された場合は、パスワードを入力後、Enterを押します。

完了後、下記コマンドを入力し[Already up-to-date]と表示されれば、Homebrewのインストールは完了です。

 $ brew update
2.pyenvのインストール
2.1. pyenvとは

pythonのバージョンを切り替えることが可能なツールで、ディレクトリ毎に設定を切り分けることが可能です。

2.2 pyenvのインストール

以下のコマンドをターミナル上で実行します。

$ brew install pyenv
3 pyenv-virtualenv
3.1 pyenv-virtualenvとは

pyenv-virtualenvとは、ディレクトリ毎に仮想環境を構築し、Pythonのバージョン、インストールしているパッケージの管理を行います。とはいえ、pyenvだけでもバージョンの管理は可能なので、どこが良いの?と言う疑問が浮上しました。調べたところ、pyenvではディレクトリ毎にPythonのバージョンを管理できるが、インストールしているモジュールはsite-packageという共通の管理ファイルを参照しているとのこと。そのため、別バージョンのモジュールを使いたい、みたいな場面には対応が難しいそうです。そこで、pyrenv-virtualenvを使用することで、このsite-packageをディレクトリ毎に管理が可能となり、別環境の影響から完全に独立することが出来る!みたいな感じです。
(もちろん、他にもメリットはあるっぽいのですが理解不足により割愛)

3.2 pyenv-virtualenvのインストール

以下のコマンドをターミナル上で実行します。

$ brew install pyenv-virtualenv
4 環境変数の設定

これだけではインストールしたpynev、pyenv-virtualenvの設定が有効化されず、デフォルトでMacに入っているPythonの設定を参照してしまいます。ルートディレクトリに存在する.bash_profileの中身を編集して、環境変数にパスを追加します。(平たく言うと、今後Pythonを使用するときは、新しく導入したpyenvとpyenv-virtualenvの方の設定を参照してねって感じ)
bash_profileは、ターミナル起動時に参照する設定ファイルのようなものです。設定ファイルには.bashrcとかもあり、詳細は理解できてないですが、取り敢えず.bash_profileを編集することで問題なく動くので良しとします。
ターミナル上で以下のコマンドを入力し、.bash_profileを編集します。
※.bash_profileは隠しファイルのため、ls -aで表示することができます。

vim .bash_profile

開いた.bash_profileに以下のコードを追記します。

export PYENV_ROOT="${HOME}/.pyenv"   //${HOME}はデフォルトで設定されている変数
if [ -d "${PYENV_ROOT}" ]; then      //パス(PYENV_ROOT)の存在確認
    export PATH=${PYENV_ROOT}/bin:$PATH  //:$PATHで、既存のパスも設定
    eval "${pyenv init-)"          //eval で変数部分を展開しています。
    eval "$(pyenv virtualenv-init -)"    //pyenvとpyenv-virtualenvの環境を初期化し、環境を有効化

上記のコードを入力後、ターミナルで以下のコマンドを入力することで、設定ファイルの更新をします。
(ターミナルを再起動するでも問題ありません。)

 source ~/.bash_profile
5. pythonのインストール

pyenvではデフォルトでsystemしか入っていません。試しに、pyenv versionsコマンドを入力すると以下のような結果が返ってきます。

pyenv versions
  *system

今回使用するPythonのバージョンは3.7.3なので、以下のコマンドでインストールします。

pyenv install 3.7.3
6. 仮想環境の構築&Pythonのバージョン変更

これで3.7.3のインストールが完了しましたが、まだ適応はされていません。もう一度、pyenv versionsを入力すると以下の結果が返ってきます。

 $ pyenv versions
   *system
    3.7.3

上記から、デフォルトであるsystemが選択されていることが分かります。作業用のディレクトリを作成し、作成したディレクトリ内で以下のコマンドを実行し、仮想環境を構築します。

 $ pyenv virtualenv 3.7.3 test //3.7.3:Pythonのバージョン
                               //test:環境名
 $ pyenv local test
 $ activate test
 (test)$ //左記のように作成した(作成した環境名)が表示されればOKです。
 (test)$ pyenv versions //Pythonのバージョンを確認
       system
       3.7.3
       *test

これで、このディレクトリではtest(3.7.3)が適応されます。

pyenv global testでも問題ありませんが、今回はディレクトリ毎で環境を切りたいので、localを使用しています。globalを使用すると、全ての環境で指定したバージョンが適応されます。

ここまでで、Pythonで開発するための環境構築が完了しました。次回の記事では、実際に機械学習で使用する各種モジュールのインストールから、簡単な実装までを書く予定です。

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

Python/NLP/機械学習のためのDocker環境構築

Docker上でPython+自然言語処理+基本的な機械学習を行うための環境構築方法について説明します.
また,Dockerについての基本的な知識があることを前提としています.

導入するもの

  • ベース
    • Alpine Linux 3.9
  • Python
    • Python 3.7.3
    • numpy
    • scipy
    • scikit-learn
    • gensim
  • 自然言語処理
    • mecab
    • cabocha

実行環境

  • Ubuntu 18.04 LTS
  • Docker 18.09.5

Alpine Linux

Alpine Linuxは, 軽量なLinuxディストリビューションです.
Dockerイメージを簡単に軽量化できることから,ベースイメージとして多く採用されています.

ただし,Alpineはデフォルトではかなり機能が制限されています.
そのため,追加で色々なツールやパッケージをインストールしていく必要があります.

各種ファイルの作成

Dockerfile

Dockerfile
# ベースイメージ
FROM python:3.7.3-alpine3.9

# 環境変数
ENV LANG C.UTF-8
ENV TZ Asia/Tokyo
ENV PYTHONUNBUFFERED 1

# 各種パッケージのインストール
RUN apk add --update bash git curl build-base swig gfortran linux-headers

# Mecab
RUN cd /tmp \
    && git clone https://github.com/taku910/mecab.git \
    && cd mecab/mecab/ \
    && ./configure --enable-utf8-only --with-charset=utf8 \
    && make \
    && make install \
    && cd ../mecab-ipadic \
    && ./configure --with-charset=utf8 \
    && make \
    && make install

# CRF++ (Cabochaで必要)
RUN wget -O /tmp/CRF++-0.58.tar.gz 'https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7QVR6VXJ5dWExSTQ' \
    && cd /tmp/ \
    && tar zxf CRF++-0.58.tar.gz \
    && cd CRF++-0.58 \
    && ./configure \
    && make \
    && make install

# Cabocha
RUN cd /tmp \
    && DOWNLOAD_URL="https://drive.google.com`curl -c cookies.txt \
       'https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7SDd1Q1dUQkZQaUU' \
       | sed -r 's/"/\n/g' |grep id=0B4y35FiV1wh7SDd1Q1dUQkZQaUU |grep confirm |sed 's/&/\&/g'`" \
    && curl -L -b cookies.txt -o /tmp/cabocha-0.69.tar.bz2 "$DOWNLOAD_URL" \
    && tar jxf cabocha-0.69.tar.bz2 \
    && cd cabocha-0.69 \
    && ./configure --with-mecab-config=`which mecab-config` --with-charset=utf8 \
    && make \
    && make install \
    && cd python \
    && python setup.py build \
    && python setup.py install \

# LAPACK/BLAS (scikit-learnで必要)
RUN cd /tmp \
    && wget http://www.netlib.org/lapack/lapack-3.8.0.tar.gz \
    && tar zxf lapack-3.8.0.tar.gz \
    && cd lapack-3.8.0/ \
    && cp make.inc.example make.inc \
    && make blaslib \
    && make lapacklib \
    && cp librefblas.a /usr/lib/libblas.a \
    && cp liblapack.a /usr/lib/liblapack.a \
    && cd / \
    && rm -rf /tmp/*

# pip
COPY requirements.txt /home
WORKDIR /home
RUN pip install --upgrade pip
RUN pip install -r requirements.txt

requirements.txt

pipでインストールするパッケージを記載します.
バージョンを指定したい場合は numpy==1.16.2 のようにしてください.
今回は必要最低限なものしかインストールしません.

requirements.txt
mecab-python3
numpy
scipy
scikit-learn
gensim

Dockerイメージを作成

Dockerfileとrequirements.txtを同じディレクトリに置きます.
以下のコマンドでDockerビルド(Dockerイメージを作成するコマンド)を実行します.

※Cabochaのダウンロードに失敗してビルドができないことがあります.
少し時間をおきながら何度か再実行してみて下さい.

$ cd <Dockerfileが存在するディレクトリ>
$ docker build -t nlp-ml:1 .
 (docker build -t <Dockerイメージ名> <Dockerfileが存在するディレクトリ>)

$ docker images
REPOSITORY  TAG   IMAGE ID       CREATED         SIZE
nlp-ml      1     4b7930388f08   3 minutes ago   93.5MB

これでDockerイメージが作成できました.
このDockerイメージを使用して,コンテナを起動します.

$ docker run -it --name hoge nlp-ml:1 bash
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonでAtCoder -ABC009-

対象

  • AtCoder初心者(緑よりもランクが下の人、ABCのCD問題が解けない人)
  • pythonがそれなりにかける人
  • 数学やプログラミングに対する深い理解を持っていない人
    • Ex) DP, 深さ優先探索, メモ化再帰の実装ができない

動機

AtCoderは全ての問題に対して解説が存在しますが
ざっくり説明を読んでわかった気になっても独力で実装ができないといった経験が多くありました。
参考のために他人の提出を見ても解説と綺麗にリンクした解答が簡単には見つからず
理解を実装に落とすことに苦労しました。

そこで、公式の解答とある程度リンクした実装を見ながら
アルゴリズムや数学的な理解とソースコードを紐付けたいと思ったことが本記事を寄稿した動機です。

いるかわかりませんが同様の苦労をしている人のためにも何らかの参考になればと思い
私の回答とそれに対する簡単なコメントを付与したコードを記載します。

実行環境

  • python3

実装

AB → 何も見ずに実装
CD → 解説を見て実装

A

A.py
import math
print(math.ceil(int(input()) / 2))

B

B.py
N = int(input())
A = list(set([int(input()) for _ in range(N)]))
A.sort()
print(A[-2])

C

C.py
from collections import Counter

N, K = map(int, input().split(' '))
S = input()
R = sorted(S)
T = ''
diff = 0

for i in range(N):
    # 各文字の出現回数をカウント
    counter = Counter(S[:i+1]) - Counter(T)
    for r in R:
        # 確定済み文字のdiff + みている文字のdiff
        diff1 = diff + (r != S[i])
        # 確定していない文字のdiff
        diff2 = sum(counter.values()) - (counter[r] > 0)
        if diff1 + diff2 <= K:
            T += r
            R.remove(r)
            diff = diff1
            break
print(T)

D

D.py
def matrix_pow(a, b):
    ret = [[0] * len(b[0]) for _ in range(len(b))]
    for x in range(len(b)):
        for y in range(len(b[0])):
            for z in range(len(b)):
                ret[x][y] ^= a[x][z] & b[z][y]
    return ret


K, M = map(int, input().split(' '))
A = list(map(int, input().split(' ')))
C = list(map(int, input().split(' ')))

if K >= M:
    print(A[M-1])
    exit()


# 漸化式計算用の行列を作成
A.reverse()
matrix = [[0] * len(C) for _ in range(len(C) - 1)]
for i in range(len(C) - 1):
    for j in range(len(C)):
        matrix[i][j] = (1 << 32) - 1 if i == j else 0
matrix.insert(0, C)

# 行列の累乗計算結果をスタック
matrix_list = [[[0] * len(C) for _ in range(len(C))] for _ in range(len(format(M-K, 'b')))]
for i in range(len(matrix_list)):
    mat = matrix if i == 0 else matrix_pow(matrix_list[i-1], matrix_list[i-1])
    for j in range(len(matrix_list[0])):
        for k in range(len(matrix_list[0][0])):
            matrix_list[i][j][k] = mat[j][k]

# フラグが立っている部分で累乗計算を適用
ans = [[a] for a in A]
for m, bit in zip(matrix_list, reversed(format(M-K, 'b'))):
    ans = matrix_pow(m, ans) if bit == '1' else ans
print(ans[0][0])

解説を見て実装をしましたが、TLEになりひたすら苦しめられました。
最終的にPypyなら通る可能性があるとの記事を発見したため、Pypyで提出したら通りました。

終わりに

D問題をpython3でACしている人が10人もおらず驚きました。(Pypyでも1ページ程度)
あまり過去問解いていく人はいないのでしょうか。。

以上です。

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

MスプラインとIスプラインをNumpyで

monotone(単調増加 or 単調減少)なスプライン、それがIスプライン。

Iスプラインを作るために使う、負にならないスプライン、それがMスプライン。

Iスプラインは、monotoneなはずのデータの回帰分析やスムージングに使えそうに見える。しかしどちらも、どうも世の中でほとんど使われていないようなので、数式をコードに起こすのに苦労した。

import numpy as np
import matplotlib.pyplot as plt

def main():
    K = 3 # 次数
    L = 4 # ノットの数
    x_min = 0.1
    x_max = 1.0
    ts = np.concatenate([
        np.full(K, x_min),
        np.linspace(x_min, x_max, L + 2)[1:-1], # ノットをuniformに配置。non-uniformでもいい
        np.full(K + 1, x_max)
    ])

    xs = np.linspace(x_min, x_max - 1e-5, 100)

    def function_m(k, i, where=np.ones_like(xs, np.bool)):
        lxs = xs[where]
        ys = np.zeros_like(lxs)
        if k == 1:
            hit = (ts[i] <= lxs) & (lxs < ts[i + 1])
            ys[hit] = 1. / (ts[i + 1] - ts[i])
        elif ts[i + k] - ts[i] != 0.:
            _a = (k - 1) * (ts[i + k] - ts[i])
            _b = lxs - ts[i]
            _c = function_m(k - 1, i, where)
            _d = ts[i + k] - lxs
            _e = function_m(k - 1, i + 1, where)
            ys = k * (_b * _c + _d * _e) / _a
        return ys

    for i in np.arange(K + L):
        plt.plot(xs, function_m(K, i), label=str(i))
    plt.legend(bbox_to_anchor=(1,1), loc='upper right')
    plt.title('M-spline')
    plt.show()

    def function_i(i):
        hit = (ts[:-1,np.newaxis] <= xs) & (xs < ts[1:,np.newaxis])
        js = np.argmax(hit, axis=0)
        def f_a(m):
            mask = js >= m
            _a = K + 1
            _b = ts[m + K + 1] - ts[m]
            _c = function_m(K + 1, m, where=mask)
            ys = np.zeros_like(xs)
            ys[mask] = _b * _c / _a
            return ys
        ufunc_a = np.vectorize(f_a, signature='()->(n)')
        yss = ufunc_a(np.arange(i, K + L))
        return yss.sum(axis=0)

    for i in np.arange(K + L):
        plt.plot(xs, function_i(i), label=str(i))
    plt.legend(bbox_to_anchor=(1,1), loc='upper right')
    plt.title('I-spline')
    plt.show()

if __name__=='__main__':
    main()

Figure_1.png

Figure_1-1.png

参考:
Wikipedia I-spline
spline
INFERENCE USING SHAPE-RESTRICTED REGRESSION SPLINES

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

Deep Learningアプリケーション開発 (7) TensorFlow Lite with Python on Raspberry Pi and Edge TPU

この記事について

機械学習、Deep Learningの専門家ではない人が、Deep Learningを応用したアプリケーションを作れるようになるのが目的です。MNIST数字識別する簡単なアプリケーションを、色々な方法で作ってみます。特に、組み込み向けアプリケーション(Edge AI)を意識しています。
モデルそのものには言及しません。数学的な話も出てきません。Deep Learningモデルをどうやって使うか(エッジ推論)、ということに重点を置いています。

  1. Kerasで簡単にMNIST数字識別モデルを作り、Pythonで確認
  2. TensorFlowモデルに変換してPythonで使用してみる (Windows, Linux)
  3. TensorFlowモデルに変換してCで使用してみる (Windows, Linux)
  4. TensorFlow Liteモデルに変換してPythonで使用してみる (Windows, Linux)
  5. TensorFlow Liteモデルに変換してCで使用してみる (Linux)
  6. TensorFlow Liteモデルに変換してC++で使用してみる (Raspberry Pi)
  7. TensorFlow LiteモデルをEdge TPU上で動かしてみる (Raspberry Pi) <--- 今回の内容

今回の内容

  • Kerasモデル(h5)を、Edge TPU用に変換する
  • Raspberry Pi上でのEdge TPU環境を用意する
  • Raspberry Piに接続されたEdge TPU上でモデルを動作させてMNIST数字識別をする

TensorFLow Lite用モデルはKerasで簡単にMNIST数字識別モデルを作り、Pythonで確認で作成したconv_mnist.h5を使います
ソースコードとサンプル入力画像: https://github.com/take-iwiw/CNN_NumberDetector/tree/master/07_TensorflowLite_TPU

環境

  • OS: Windows 10 (64-bt)
  • CPU = Intel Core i7-6700@3.4GHz (物理コア=4、論理プロセッサ数=8)
  • Raspberry Pi 2 Model B (2018-11-13-raspbian-stretch)
  • その他: Google Edge TPU

Keras用モデルをEdge TPU用モデルに変換する

量子化したTensorFlow Liteモデルに変換する

Edge TPU上で動作可能なモデルは、いくつかの制約を満たしたTensorFlow Liteモデル(tflite)になります。(https://coral.withgoogle.com/web-compiler/ のRequirementsセクションを参照)
そのため、まずはKerasで作成したモデル(h5)をTensorFlow Liteモデル(tflite)に変換します。その際に、上記制約を満たすために、量子化オプションも付けます。
変換ツール (tflite_convert)の使い方は、https://www.tensorflow.org/lite/convert/cmdline_examples を参考にしてください。

これによって、conv_mnist.tfliteというファイルが出来上がります。

Kerasモデルから量子化済みTensorFlowLiteモデルに変換
tflite_convert ^
  --output_file=conv_mnist.tflite --keras_model_file=conv_mnist.h5 ^
  --inference_type=QUANTIZED_UINT8 ^
  --default_ranges_min=0 ^
  --default_ranges_max=255 ^
  --mean_values=128 ^
  --std_dev_values=127

変換はWindows上のAnaconda環境(python=3.6.8, tf-nightly-gpu=1.14.1.dev20190403)で行いました。
TensorFlowをpipインストールすると、自動的にtflite_convertもインストールされます。

Edge TPU用モデルに変換する

次に、作成したTensorFlow Liteモデルを、Edge TPU用に変換します。これには、Googleが提供しているWebコンパイラを使用します。
https://coral.withgoogle.com/web-compiler/

Drag and drop your model file here の所に、先ほど作成したconv_mnist.tfliteをドラッグ&ドロップし、使用許諾的な2つのチェックボックスにチェックを付けると、自動的にコンパイルが始まります。

Clipboard02.jpg

成功したら下記画面のようになるので、Download modelで、ダウンロードします。
conv_mnist_123456789_edgetpu.tflite というファイルがダウンロードされます。扱いやすいように、数字部分は消してconv_mnist_edgetpu.tflite としておきます。

Clipboard01.jpg

備考:
2019年4月11日まではベータ版ということで、MobileNetなど、限られたモデルしか対応していませんでした。4月12日(この記事を書いている前日)から、カスタムモデルにも対応されるようになりました!!

Raspberry Pi上でのEdge TPU環境を用意する

基本的には、https://coral.withgoogle.com/docs/accelerator/get-started/ の通りに進めれば大丈夫です。
ラズパイ上の端末で以下コマンドでインストールします。

EdgeTPUインストールonラズパイ
cd ~/
wget https://dl.google.com/coral/edgetpu_api/edgetpu_api_latest.tar.gz -O edgetpu_api.tar.gz --trust-server-names
tar xzf edgetpu_api.tar.gz
cd edgetpu_api
bash ./install.sh

が、僕の環境はRaspberry Pi 2のため、Edge TPUは未対応のためインストールに失敗しました。また、virtualenv上ではうまくインストールできませんでした。
以下方法で回避しましたので、同じ状況の人は参考にしてください。(自己責任でお願いします)

Raspberry Pi 2にインストールする(optional)

install.sh でプラットフォームチェックしている部分を以下のように変更します。
ラズパイ3でも32bitモードで動いているので、バイナリ互換性はあるはず、と思いチェック処理だけ変えたら出来ました。

install.sh
~略~
elif [[ "${CPU_ARCH}" == "armv7l" ]]; then
  MODEL=$(cat /proc/device-tree/model)
  # if [[ "${MODEL}" == "Raspberry Pi 3 Model B Rev"* ]]; then
  if [[ "${MODEL}" == "Raspberry Pi "* ]]; then
    info "Recognized as Raspberry Pi 3 B."
    LIBEDGETPU_SUFFIX=arm32
    HOST_GNU_TYPE=arm-linux-gnueabihf
  # elif [[ "${MODEL}" == "Raspberry Pi 3 Model B Plus Rev"* ]]; then
  elif [[ "${MODEL}" == "Raspberry Pi "* ]]; then
    info "Recognized as Raspberry Pi 3 B+."
    LIBEDGETPU_SUFFIX=arm32
    HOST_GNU_TYPE=arm-linux-gnueabihf
  fi
~略~

virtualenv上にインストールする(optional)

単にinstall.sh を実行すると、パッケージはグローバル環境にインストールされるようです。(virtualenvをactivate/workonしていても)。
そのため、自分の仮想環境に入っている状態で、別途pip installする必要がありました。
これをやらないと、No module named 'edgetpu' エラーが出ます。

まとめると以下のようになります。

EdgeTPUインストールfor仮想環境onラズパイ2
cd ~/
workon py3_tpu

wget https://dl.google.com/coral/edgetpu_api/edgetpu_api_latest.tar.gz -O edgetpu_api.tar.gz --trust-server-names
tar xzf edgetpu_api.tar.gz
cd edgetpu_api
nano ./install.sh
# 編集する
bash ./install.sh

pip install edgetpu-1.9.2-py3-none-any.whl

Edge TPU上でモデルを動作させてMNIST数字識別をする

Edge TPUのインストールを完了すると、デモコードが/usr/local/lib/python3.5/dist-packages/edgetpu/ (通常インストールの場合)、/home/pi/.virtualenvs/py3_tpu/lib/python3.5/site-packages/edgetpu (virtualenvの場合)にインストールされます。

デモコードを動かす

xxx/site-packages/edgetpu/demo/classify_image.py が識別用のサンプルコードです。
本当なら、以下のようにすることでデモコードで、今回作成したモデルを試すことが出来ます。
が、これは動きません。
デモコード(というか、画像識別用Wrapper)が、3chカラー入力を前提としているため、今回作成した1chグレースケールのMNIST識別モデルはパラメータチェックではじかれてエラーになります。
MobileNet等のちゃんとしたモデルなら、これで確認出来ます。

デモコードを動かす
python3 /home/pi/.virtualenvs/py3_tpu/lib/python3.5/site-packages/edgetpu/demo/classify_image.py \
--model conv_mnist_edgetpu.tflite \
--label mnist_labels.txt \
--image resource/3.jpg

ちなみに、mnist_labels.txtには識別結果と表示ラベルのペアを記載します。

mnist_labels.txt
0 num0
1 num1
2 num2
~略~

識別用コードを実装する

edgetpu/demo/classify_image.pyをベースに、自分で実装します。classify_image_mnist.pyとします。

classify_image_mnist.py
import argparse
import re
from edgetpu.classification.engine import ClassificationEngine
from PIL import Image, ImageOps
import numpy

# Function to read labels from text files.
def ReadLabelFile(file_path):
  """Reads labels from text file and store it in a dict.

  Each line in the file contains id and description separted by colon or space.
  Example: '0:cat' or '0 cat'.

  Args:
    file_path: String, path to the label file.

  Returns:
    Dict of (int, string) which maps label id to description.
  """
  with open(file_path, 'r', encoding='utf-8') as f:
    lines = f.readlines()
  ret = {}
  for line in lines:
    pair = re.split(r'[:\s]+', line.strip(), maxsplit=1)
    ret[int(pair[0])] = pair[1].strip()
  return ret


def main():
  parser = argparse.ArgumentParser()
  parser.add_argument(
      '--model', help='File path of Tflite model.', required=True)
  parser.add_argument(
      '--label', help='File path of label file.', required=True)
  parser.add_argument(
      '--image', help='File path of the image to be recognized.', required=True)
  args = parser.parse_args()

  # Prepare labels.
  labels = ReadLabelFile(args.label)
  # Initialize engine.
  engine = ClassificationEngine(args.model)

  # Read input image and convert to tensor
  img = Image.open(args.image)
  img = img.convert('L')
  img = ImageOps.invert(img)
  input_tensor_shape = engine.get_input_tensor_shape()
  if (input_tensor_shape.size != 4 or input_tensor_shape[3] != 1 or
      input_tensor_shape[0] != 1):
    raise RuntimeError(
        'Invalid input tensor shape! Expected: [1, height, width, 3]')
  _, height, width, _ = input_tensor_shape
  img = img.resize((width, height), Image.NEAREST)
  input_tensor = numpy.asarray(img).flatten()

  # Run inference.
  for result in engine.ClassifyWithInputTensor(input_tensor=input_tensor, threshold=0.1, top_k=3):
    print('---------------------------')
    print(labels[result[0]])
    print('Score : ', result[1])

  # for time measurement
  for i in range(5000):
    for result in engine.ClassifyWithInputTensor(input_tensor=input_tensor, threshold=0.1, top_k=3):
      pass

if __name__ == '__main__':
  main()

オリジナルのデモコードでは、読み込んだ画像をそのまま入力として、ClassificationEngine.ClassifyWithImageをコールしていました。
この関数は画像を入れるだけで推論処理が出来て便利なのですが、グレースケール(1ch)画像には対応していないです。
ClassificationEngine.ClassifyWithImageは、内部でリサイズなどを行ったあと、ClassificationEngine.ClassifyWithInputTensor を呼んでいます。こちらの関数の入力は文字通りTensorになります。
ですので、今回は自分で画像->Tensorへの変換を行い、直接ClassificationEngine.ClassifyWithInputTensor を呼ぶことにしました。

また、処理時間測定のために無駄に5000回くらい呼んでいます。

実行してみる

以下コマンドで実行できます。

実行コマンド
python3 classify_image_mnist.py \
--model ~/CNN_NumberDetector/07_TensorflowLite_TPU/conv_mnist_edgetpu.tflite \
--label ~/CNN_NumberDetector/07_TensorflowLite_TPU/mnist_labels.txt \
--image ~/CNN_NumberDetector/07_TensorflowLite_TPU/resource/5.jpg

「5」が書かれたJPEG画像を入力しました。結果、以下のように正しく5だと識別できました。
また、実行中はEdgeTPUドングルの白LEDが強く光っていたので、ちゃんとTPU側で処理しているのだと思います。

結果
num5
Score :  0.99609375

処理時間を比較してみる

使用するモデルを差し替えるだけで、CPU動作かEdge TPU動作かを切り替えられるようです。
timeコマンドを付けて処理時間を比較してみました。

処理時間比較実行コマンド
# for Edge TPU
time python3 classify_image_mnist.py \
--model ~/CNN_NumberDetector/07_TensorflowLite_TPU/conv_mnist_edgetpu.tflite \
--label ~/CNN_NumberDetector/07_TensorflowLite_TPU/mnist_labels.txt \
--image ~/CNN_NumberDetector/07_TensorflowLite_TPU/resource/5.jpg

# for CPU
time python3 classify_image_mnist.py \
--model ~/CNN_NumberDetector/07_TensorflowLite_TPU/conv_mnist.tflite \
--label ~/CNN_NumberDetector/07_TensorflowLite_TPU/mnist_labels.txt \
--image ~/CNN_NumberDetector/07_TensorflowLite_TPU/resource/5.jpg
結果
Edge TPU:
real    0m18.596s
user    0m9.698s
sys 0m3.427s

CPU:
real    0m12.852s
user    0m9.446s
sys 0m0.233s

結果、Edge TPU実行はトータルで18.6秒、CPU実行はトータルで12.9秒という、CPUの方が速いという残念な結果になってしまいました。
sys時間を見ると、TPU側の方は3.5秒もかかっていました。おそらく、EdgeTPUデバイス制御関係のオーバーヘッドによるものだと思います。
今回使用したような非常に小さいモデルではディープラーニング計算時間そのものよりも、こういったオーバーヘッドの方が大きいようです。

メモ: インストールコマンドまとめ

素のRaspberry Pi2に対して、Edge TPUデモコードを動作させるまでの環境設定コマンド
(virtualenv, opencvを含む(単にEdge TPUを試すだけなら不要))

EdgeTPU環境設定コマンドforラズパイ2
# 環境更新
sudo apt-get update
sudo apt-get upgrade

# OpenCV用パッケージインストール
sudo apt-get install libatlas3-base libwebp6 libtiff5 libjasper1 libilmbase12 libopenexr22 libilmbase12 libgstreamer1.0-0 libavcodec57 libavformat57 libavutil55 libswscale4 libqtgui4 libqt4-test libqtcore4

# virtualenvインストールと設定
sudo pip install virtualenv
sudo pip install virtualenvwrapper

nano ~/.bashrc 
>>> 以下を追加
export LC_ALL="en_US.UTF-8"
export LC_CTYPE="en_US.UTF-8"
### Virtualenvwrapper
if [ -f /usr/local/bin/virtualenvwrapper.sh ]; then
    export WORKON_HOME=$HOME/.virtualenvs
    source /usr/local/bin/virtualenvwrapper.sh
fi
<<<

# ターミナル再起動

# 仮想環境を作る (名前をpy3_tpuとする)
cd ~/
mkvirtualenv --python=python3 py3_tpu
# deactivate
# workon py3_tpu

# 仮想環境上で必要なパッケージをpipインストール
pip install --upgrade setuptools
pip install opencv-python
pip install numpy

# Pillow は2019/4/13時点でインストール時にビルドエラーが出たので、piwheelsのprebuiltバイナリを使用
# pip install Pillow
pip install https://www.piwheels.org/simple/Pillow/Pillow-5.4.1-cp35-cp35m-linux_armv7l.whl#sha256=ed4e05737ed076aed4065e05b67983650b10110f3e133444e8e87bd572e1492a

# Edge TPUインストール
wget https://dl.google.com/coral/edgetpu_api/edgetpu_api_latest.tar.gz -O edgetpu_api.tar.gz --trust-server-names
tar xzf edgetpu_api.tar.gz
cd edgetpu_api

# ラズパイ2の場合、編集する(https://qiita.com/take-iwiw/items/6aeab468c326ecc21563#raspberry-pi-2にインストールするoptional)
nano ./install.sh

bash ./install.sh
# 仮想環境用に再度インストール
pip install edgetpu-1.9.2-py3-none-any.whl


# デモを試す
cd ~/Downloads/
wget https://storage.googleapis.com/cloud-iot-edge-pretrained-models/canned_models/mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.tflite \
http://storage.googleapis.com/cloud-iot-edge-pretrained-models/canned_models/inat_bird_labels.txt \
https://coral.withgoogle.com/static/images/parrot.jpg

cd /home/pi/.virtualenvs/py3_tpu/lib/python3.5/site-packages/edgetpu/demo
python3 classify_image.py \
--model ~/Downloads/mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.tflite \
--label ~/Downloads/inat_bird_labels.txt \
--image ~/Downloads/parrot.jpg

※たまたまかもしれないが、インストール直後にそのままデモを実行したら、RuntimeErrorが発生した。ラズパイを再起動したりEdge TPUドングルを抜き差ししたら治った。

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

Edge TPU Accelarator+カスタムモデルMobileNetv2-SSDLiteの.tflite生成 【やっと成功したよ】_Docker編_その4

Tensorflow-bin GitHub stars

TPU-MobilenetSSD GitHub stars

1.Introduction

TPUモデルコンパイラがカスタムモデルの変換に対応したそうですので早速試してみました。
この記事の手順では最終的に生成されたモデルの精度が極悪ですが、転移学習ではなくフル学習にしたり、パラメータを調整することで精度は改善可能です。
この記事で語るのは、あくまで 独自DockerイメージによるカスタムTPUモデルの学習と生成手順 のみです。
モデル性能のチューニングは実施しません。

2.Procedure

学習手順は、前回記事 Edge TPU Accelaratorの動作を少しでも高速化したかったのでMobileNetv2-SSD/MobileNetv1-SSD+MS-COCOをPascal VOCで転移学習して.tfliteを生成した_Docker編_その2 あるいは Edge TPU Accelaratorの動作を少しでも高速化したかったのでMS-COCOをPascal VOCで転移学習して.tfliteを生成した_GoogleColaboratory[GPU]編_その3 をそのまま使用します。 前回記事はもともと、 TPUコンパイラ がカスタムモデルに対応することを事前に知っていたため、機能がリリースされたときに最速で検証できるよう事前準備をしていたのです。
私独自のDockerは公式のDockerイメージとは異なり、NVIDIA Docker を使用します。
公式のDockerがGPUトレーニングに対応していないため、自力でシーケンスを大幅に組み直しています。

なお、対応可能なレイヤと変換アルゴリズムは、 公式の TensorFlow models on the Edge TPU に記載されていますので参考にしてください。

3.Results

前回記事 の手順により生成した MobileNetv2-SSDLite の .tflite ファイルをコンパイラに投入してみます。

https://coral.withgoogle.com/web-compiler/
FireShot Capture 008 - Edge TPU Model Compiler - Coral - coral.withgoogle.com.png

ドラッグ&ドロップで、 せーの、ホイッ!
FireShot Capture 006 - Edge TPU Model Compiler - Coral - coral.withgoogle.com.png

あっさり成功しました。 しかも、アップデートが入る前までは一切表示されなかった変換ログが表示されるようになっています。 さらに、TPU側で非対応のレイヤーは自動的にCPUへオフロードされる仕様に改善されています。
最高です。 OpenVINOに欲しい機能です。
ある程度自由にレイヤを組み合わせて推論させることができるため、対応モデルの幅が大幅に広がりましたね。

今回は、各種公式ドキュメントを一切見ずに独自の軽量トレーニングとコンバートを実施しました。

4.Finally

おいおいフルトレーニングをし直して推論精度を向上した .tflite モデルを生成して遊びたいと思います。

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

OpenAIGymのCartPoleをCartほむほむ化する

痛RL環境を作りたい

強化学習(Reinforcement Learning, RL)を使って研究をしているとき,
研究や仕事のモチベを上げるために痛エディタがあるなら
痛RL環境があってもいいじゃない.
と思いたったので作ってみた.
natm.gif
かわいい.

OpenAIGymのCartPoleのPole部分を画像に差し替える

OpenAIGymのパッケージのフォルダを開いて
gym/envs に MyEnv フォルダを作る.
中身は以下のとおり.

gym/envs/MyEnv
 ├ __init__.py
 ├ cartpole_img.py
 └ assets/
  └ pole_img.png

gym/envs/MyEnv/cartpole_img.py の中身

CartPoleEnv_Img クラスを作り,
既存の gym/envs/classic_control/cartpole.py の中にある CartPoleEnv クラスの
render メソッドをコピペしてきてちょっと変える.
ちなみに画像読み込み周りの処理は gym/envs/classic_control/pendulum.py からのコピペ.

gym/envs/MyEnv/cartpole_img.py
from os import path
from gym.envs.classic_control.cartpole import CartPoleEnv

class CartPoleEnv_Img(CartPoleEnv):     # 既存のCartPoleEnvクラスを継承
    def render(self, mode='human'):
        screen_width = 600
        screen_height = 400

        world_width = self.x_threshold*2
        scale = screen_width/world_width
        carty = 100 # TOP OF CART
        polewidth = 10.0
        polelen = scale * (2 * self.length)
        cartwidth = 50.0
        cartheight = 30.0

        if self.viewer is None:
            from gym.envs.classic_control import rendering
            self.viewer = rendering.Viewer(screen_width, screen_height)
            l,r,t,b = -cartwidth/2, cartwidth/2, cartheight/2, -cartheight/2
            axleoffset =cartheight/4.0
            cart = rendering.FilledPolygon([(l,b), (l,t), (r,t), (r,b)])
            self.carttrans = rendering.Transform()
            cart.add_attr(self.carttrans)
            self.viewer.add_geom(cart)
            l,r,t,b = -polewidth/2,polewidth/2,polelen-polewidth/2,-polewidth/2

            # Pole部分を作る元々の処理をコメントアウト
            # pole = rendering.FilledPolygon([(l,b), (l,t), (r,t), (r,b)])
            # pole.set_color(.8,.6,.4)

            # 改変部分
            fname = path.join(path.dirname(__file__), "assets/pole_pic.png")    # 画像指定
            scale = 200.
            pole = rendering.Image(fname, scale, scale*2.)  # 画像の大きさ指定
            pole.set_color(1, 1, 1)     # 画像の色指定

            self.poletrans = rendering.Transform(translation=(0, axleoffset))
            pole.add_attr(self.poletrans)
            pole.add_attr(self.carttrans)
            self.viewer.add_geom(pole)
            self.axle = rendering.make_circle(polewidth/2)
            self.axle.add_attr(self.poletrans)
            self.axle.add_attr(self.carttrans)
            self.axle.set_color(.5,.5,.8)
            self.viewer.add_geom(self.axle)
            self.track = rendering.Line((0,carty), (screen_width,carty))
            self.track.set_color(0,0,0)
            self.viewer.add_geom(self.track)

            self._pole_geom = pole

        if self.state is None: return None

        # Edit the pole polygon vertex
        pole = self._pole_geom
        l,r,t,b = -polewidth/2,polewidth/2,polelen-polewidth/2,-polewidth/2
        pole.v = [(l,b), (l,t), (r,t), (r,b)]

        x = self.state
        cartx = x[0]*scale+screen_width/2.0 # MIDDLE OF CART
        self.carttrans.set_translation(cartx, carty)
        self.poletrans.set_rotation(-x[2])

        return self.viewer.render(return_rgb_array = mode=='rgb_array')

gym/envs/MyEnv/__init__.py の中身

作ったクラスをインポート.

gym/envs/MyEnv/__init__.py
from gym.envs.MyEnv.cartpole_img import CartPoleEnv_Img

gym/envs/MyEnv/assets/pole_pic.png

pole_pic.png には好きな画像を用意すればいいが,
棒の支点にあたる部分が中心に来るような画像にしておく.
筆者はAzPainterを使って作った.
キャプチャ.PNG

自作環境を使えるようにする

一つ上の階層 gym/envs/ にある __init__.py に,
自作環境の情報を追加しておく.

gym/envs/__init__.py
~~~省略~~~

# Classic
# ----------------------------------------

register(
    id='CartPole-v0',
    entry_point='gym.envs.classic_control:CartPoleEnv',
    max_episode_steps=200,
    reward_threshold=195.0,
)

register(
    id='CartPole-v1',
    entry_point='gym.envs.classic_control:CartPoleEnv',
    max_episode_steps=500,
    reward_threshold=475.0,
)

# ここに追加
register(
    id='CartPoleImg-v0',
    entry_point='gym.envs.MyEnv:CartPoleEnv_Img',
    max_episode_steps=200,
    reward_threshold=195.0,
)

register(
    id='MountainCar-v0',
    entry_point='gym.envs.classic_control:MountainCarEnv',
    max_episode_steps=200,
    reward_threshold=-110.0,
)

register(
    id='MountainCarContinuous-v0',
    entry_point='gym.envs.classic_control:Continuous_MountainCarEnv',
    max_episode_steps=999,
    reward_threshold=90.0,
)

register(
    id='Pendulum-v0',
    entry_point='gym.envs.classic_control:PendulumEnv',
    max_episode_steps=200,
)

register(
    id='Acrobot-v1',
    entry_point='gym.envs.classic_control:AcrobotEnv',
    reward_threshold=-100.0,
    max_episode_steps=500,
)

~~~省略~~~

これで,env = gym.make("CartPoleImg-v0") できるようになる.

一応

版権絵を使うときは個人利用に留めましょう.
怒られても筆者は責任を負わない.

参考ページ

https://qiita.com/ohtaman/items/edcb3b0a2ff9d48a7def

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

sklearn.datasetsのmake_blobsのfeatureとcenterについて調べてみた

sklearn.datasets.make_blobsのfeatureとcenterについて

featureについて

from sklearn.datasets.samples_generator import make_blobs
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

X, y = make_blobs(n_features=2, centers=3)
X_train, X_test, y_train, y_test = train_test_split(X, y)
model = KNeighborsClassifier()
model.fit(X_train, y_train)

y_pred = model.predict(X_test)

acc = accuracy_score(y_test, y_pred) * 100
print('accuracy: {}%'.format(acc))

n_featuresはどれだけのカラムまたは特徴量をデータセットに定義するかを決定する。

centerについて

center : intまたは形状の配列[n_centers、n_features]、オプション
(デフォルト=なし)生成する中心の数、または固定中心位置。n_samplesがintで、
centerがNoneの場合、3つの中心が生成されます。n_samplesが配列に似ている場合、centerは>Noneまたはn_samplesの長さに等しい長さの配列のいずれかでなければなりません。
よくわからないので、プロットしてみた。

# plot 1 (centers=1)
X, y = make_blobs(n_features=2, centers=1)
plt.figure()
plt.scatter(X[:, 0], X[:, 1], c=y)
plt.savefig('centers_1.png')
plt.title('centers = 1')

スクリーンショット 2019-04-13 1.27.59.png

# plot 2    ('centers = 2')
X, y = make_blobs(n_features=2, centers=2)
plt.figure()
plt.scatter(X[:, 0], X[:, 1], c=y)
plt.title('centers = 2')

スクリーンショット 2019-04-13 1.28.06.png

# plot 3 ('centers = 3')
X, y = make_blobs(n_features=2, centers=3)
plt.figure()
plt.scatter(X[:, 0], X[:, 1], c=y)
plt.title('centers = 3')

スクリーンショット 2019-04-13 1.28.13.png

これらよりcenterはそれぞれのブロブ(塊)をセンター毎に分けて生成されていることがわかる。

引用

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

Pythonの環境構築をしてみた

やったこと

 1. Anacondaの導入
 2. Pycharmの導入
 3. AnacondaとPycharmの連携

1. Anacondaの導入

 以下のリンクからダウンロードする。それだけ。

 https://www.anaconda.com/distribution/

※Python3.7とPython2.7を選択できるが、特別な理由がない限り最新のPython3.7を選択するとよいでしょう。なぜなら、今後新機能がリリースされる際、基本的にPython3.7にのみ追加されることとなり、Python2.7が改善されることはないので。とはいえ、Python2.xも2020年まではサポート期間が残ってはいますが。

2. Pycharmの導入

 以下のリンクからダウンロードする。それだけ。

 https://www.jetbrains.com/pycharm/?fromMenu

※pycharmにはprfessional (有料版)と community (無料版)とがありますが、以下にざっくりと違いをまとめるので、各自で判断してダウンロードするとよいでしょう。

◯professionalにあって、communityにはないもの

・データベースサポートがない
・Web 系のコードがすべて補完が効かない
・IDE 上からデプロイができない
・Django サポートがない

3. AnacondaとPycharmの連携

 せっかくAnacondaとPycharmをインストールできたので、両者を連携して快適に開発したいと思うでしょう。ということで、両者の連携の仕方を簡単に記録します。

 1. AnacondaのPathを調べる
 2. Pycharmのproject inspecterにPathを設定する

以上。非常にシンプルに書きましたが、これ以上複雑に書けないくらい簡単でした。

 ちなみに以下の記事は画像付きでもっと親切かと思うので、参考にするとよいかもしれないです。

 https://qiita.com/aburafia/items/e783aa966f77448f44d1

さいごに

非常に簡単にPythonの開発環境を整えたときの話を書きましたが、Anacondaが開発環境をまるごとインストールしてくれるので、簡単に環境構築することができました。

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

IPython起動時にいつも使うモジュールをimportする設定

やりたいこと

いつも使うモジュールを毎回importするのがめんどくさいので、起動時にimportしといてもらいたい。

tl;dr

~/.ipython/profile_default/hoge.py以下にimport部分を書いておく。

IPython startup directory

たぶん、~/.ipython/profile_default/ここにディレクトリがある。
そこにREADMEがあって、
1. ここにある.py.ipyファイルを実行前に読み込んでくれる。
2. ファイル名の辞書順で実行してくれる。
的なことが書いてある。

使ってみる

適当に~/.ipython/profile_default/00-startup.pyに以下を書く。

~/.ipython/profile_default/00-startup.py
from collections import Counter
import pickle


def pload(fname):
  return pickle.load(open(fname, 'rb'))


def pdump(object, fname):
  pickle.dump(object, open(fname, 'wb'))

ipython起動して、簡単にテスト

test.
$ ipython
In [1]: pdump([1,2,3], "hoge")                                                                                                                                           

In [2]: pload("hoge")                                                                                                                                                    
Out[2]: [1, 2, 3]

In [3]: Counter("HJKLHJKLLKJLJKJKJLKJHKJJLHKLJLK")                                                                                                                                   
Out[3]: Counter({'H': 4, 'J': 10, 'K': 9, 'L': 8})

その他

入れすぎたり、重い処理を書くと起動が遅くなるから注意。
jupyter notebook, jupyter labとかも同じ設定でいけると思う。
いちおうipython hoge.pyってやれば、事前にimportしてからhoge.pyを実行くれる。
普通のpythonで起動するインタラクティブシェルの場合は環境変数のPYTHONSTARTUPにファイルのパスを設定すればいい。 https://docs.python.org/ja/3/using/cmdline.html#envvar-PYTHONSTARTUP

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

宴会の幹事決めプログラム(イカサマもできますがやらないでくださいね)

1. 要約

 宴会の幹事決めるのって難しくない?先輩に任せっきりもアレだし,後輩に押し付けるのもアレだし...そんなお悩みを持つあなたのために,宴会の幹事をランダムに決定するプログラムを書きました.これで誰が選ばれても「仕方がないナァ~~~」という空気になります.

2.核となるのはrandomモジュールのchoice()

 pythonには強力なライブラリがあります.今回はrandom.choice()という関数を利用して,幹事候補者のリストの中から一名をランダムに決定するという処理を行います.aをリストとすると,random.choice(a)はリストの要素を一つランダムに選択する関数です.そのため,幹事候補者のリストを作成することが必要となります.今回作成したプログラムは以下のステップで構成されています(フローチャート?そんなもん知らねぇ!!).

  • step 1. 候補者の人数を入力
  • step 2. 候補者を一人ずつリストに格納
  • step 3. 候補者リストを表示して間違い,抜け漏れがないか確認を取る
  • step 4. random.choice()で候補者リストから一人選択し,画面に表示する

以上の動作に,ユーザーからの入力が正しいかどうかの判定を加えたのが今回のプログラムです.

3.幹事決めプログラム

いつものように,ターミナルやコマンドプロンプトで実行するプログラムです.

python, kanji_gime.py
# -*- coding: utf-8  -*- 
import random

"""
input
    n: number of candidate of KANJI
    name: name of candidates
output
    kanji: KANJI
"""
ans = 0

while True:
    n = float(input(u"幹事候補者の人数を入れてください(int)->  "))
    if n < 0 or n.is_integer() == False:
        print(u"\nnには正整数入れてや\n")
        continue
    elif n.is_integer():
        n = int(n)
        break
while True:
    if ans == "y":
        break
    name_list = []
    for i in range(n):
        print("%d人目" % (i+1))
        name_list.append(input(u"候補者の名前を入力してください: "))
    print(name_list)
    while True:
        ans = input(u"\n候補者リストはこれ↑で合っていますか?(y/n): ")
        if ans == 'y':
            break
        elif ans == 'n':
            print(u"やり直しやで~~~")
            break
        else:
            print(u"y(yes)かn(no)で答えてちょっ!!!")

kanji = random.choice(name_list)
print(u"\n今回の幹事は",kanji,"さんです")

(でもnの入力が数値以外だとエラーが出て止まります.直せる方は直して使ってください.)
乱数固定するとイカサマできます.

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