msdd’s blog

deep learning勉強中。プログラム関連のこと書きます。

Githubのトレンドを取得するGithub Unofficial Trending API

はじめに

f:id:msdd:20200423224651p:plain

GithubTrendingをコードから取得したいと思いAPIがないか探した。 Github API v4のドキュメントでtrendingがつくものを探していたが見つからなかった。 他でも探していると、 stackoverflowでの回答や、 Githubのコミュニティフォーラム に公式のAPIはないようなことが書かれていた。 そこで、非公式のもので、Trendingを取得できるものを見つけたので、それを使ってみようと思う。

Github Unofficial Trending API

GithubAPIで不足しているTrendingを取得できる非公式API

ブラウザで試してみる

試しに、ブラウザにhttps://ghapi.huchen.dev/repositories?language=javascript&since=weeklyを入力すると、結果が表示される。 これは、言語がjavascriptで期間がweeklyつまり一週間のトレンドを取得するものである。

f:id:msdd:20200423215802p:plain
ブラウザで試した画面

json形式の結果が得られる。下が結果の一部で、リポジトリ名を確認してみると 1番目は、project_corona_trackerで、 2番目がdecentralized-video-chartということがわかる。

[
  {
    "author": "adrianhajdin",
    "name": "project_corona_tracker",
    "avatar": "https://github.com/adrianhajdin.png",
    "url": "https://github.com/adrianhajdin/project_corona_tracker",
    "description": "This is a code repository for the corresponding YouTube video. In this tutorial we are going to build and deploy a corona tracker application. Covered topics: React.js, Chart.js, Material UI and much more.",
    "language": "JavaScript",
    "languageColor": "#f1e05a",
    "stars": 341,
    "forks": 89,
    "currentPeriodStars": 178,
    "builtBy": [
      {
        "username": "adrianhajdin",
        "href": "https://github.com/adrianhajdin",
        "avatar": "https://avatars3.githubusercontent.com/u/24898559"
      }
    ]
  },
  {
    "author": "ianramzy",
    "name": "decentralized-video-chat",
    "avatar": "https://github.com/ianramzy.png",
    "url": "https://github.com/ianramzy/decentralized-video-chat",
    "description": "🚀 Zipcall.io 🚀 Peer to peer browser video calling platform with unmatched video quality and latency.",
    "language": "JavaScript",
    "languageColor": "#f1e05a",
    "stars": 1945,
    "forks": 183,

これをGithubのTrendingのページで確認してみる。上の結果と同じで、 きちんと、言語がjavascript、期間がweeklyのtrendingを取得できていることがわかる。

f:id:msdd:20200423215701p:plain

使い方

Trendingは

がある。

リポジトリのトレンドを取得

ベースとなるurl

https://ghapi.huchen.dev/repositories

にパラメータを足していく。パラメータを足すときは、urlの後ろに?をつけて、parameter名=値という形で追加していく。複数あるときは、&でつなぐ。

パラメータ名 説明
language プログラミング言語(e.g. javascript,python,c%23(c#) など)
since 期間 (daily,weekly,monthlyから選ぶ。)
spoken_language_code 話す言語(ja,enなど)

language : https://github.com/huchenme/github-trending-api/blob/master/src/languages.json
spoken_language_code : https://github.com/huchenme/github-trending-api/blob/master/src/spoken-languages.json
にあるurlParamの値。

https://ghapi.huchen.dev/repositories?since=daily&spoken_language_code=ja

結果はリスト形式で得られる。そのリストの個別のオブジェクトは下のようになっている。

{
    "author": "リポジトリのオーナ",
    "name": "リポジトリ名",
    "avatar": "アバターのurl",
    "url": "リポジトリurl",
    "description": "リポジトリ説明",
    "language": "プログラミング言語",
    "languageColor": "githubのプログラミング言語の色",
    "stars": スター数,
    "forks": フォーク数,
    "currentPeriodStars": 期間内についたスター数,
    "builtBy": [
        {
            "username": "ユーザー名",
            "href": "ユーザーurl",
            "avatar": "アバターurl"
        },
        {
            "username": "ユーザー名",
            "href": "ユーザーurl",
            "avatar": "アバターurl"
        }
    ]
}

開発者のトレンドを取得

ベースとなるurl

https://ghapi.huchen.dev/developers
パラメータ名 説明
language プログラミング言語(e.g. javascript,python,c%23(c#) など)
since 期間 (daily,weekly,monthlyから選ぶ。)

https://ghapi.huchen.dev/developers?language=javascript&since=daily

結果はリスト形式で得られる。そのリストの個別のオブジェクトは下のようになっている。

{
    "username": "ユーザー名",
    "name": "変更できる名前",
    "url": "ユーザーurl",
    "avatar": "アバターurl",
    "repo": {
        "name": "人気のリポジトリ名",
        "description": "リポジトリ説明",
        "url": "リポジトリのurl"
    }
}

pythonで実行

import urllib.request
import json


endpoint_repo="https://ghapi.huchen.dev/repositories"
endpoint_developer="https://ghapi.huchen.dev/developers"


def get_trending_repositories(language=None,\
since="daily",spoken_language_code=None):
    params=""
    params+="?since="+since

    if language is not None:
        params+=("&language="+language)

    if spoken_language_code is not None:
        params+=("&spoken_language_code="+spoken_language_code)

    url=endpoint_repo+params

    with urllib.request.urlopen(url) as res:
        body=res.read()
        j=json.loads(body)

        return j


def get_trending_developers(language=None,since="daily"):
    params=""
    params+="?since="+since

    if language is not None:
        params+=("&language="+language)

    url=endpoint_developer+params

    with urllib.request.urlopen(url) as res:
        body=res.read()
        j=json.loads(body)

        return j

レポジトリのtrendingを取得してみる。期間:今日、話し言語は:日本語、プログラミング言語:pythonで実行してみた。

def show_trending_repos(since="daily",language=None,spoken_language_code=None):
    result=get_trending_repositories(since=since,language=language,\
        spoken_language_code=spoken_language_code)
    for i,repo in enumerate(result):
        print(i,"番目")
        print("リポジトリ名:",repo["name"])
        print("url:",repo["url"])
        print()

        with open("test.json","w",encoding="utf-8") as f:
            json.dump(repo,f,indent=4)

if __name__=="__main__":
    show_trending_repos(since="monthly",language="python",\
        spoken_language_code="ja")

結果は下のようになった。trendingと一致している。trendingの数は、25個などの時もあれば、0個や1個の時もある。

0 番目
リポジトリ名: deep-learning-from-scratch
url: https://github.com/oreilly-japan/deep-learning-from-scratch

開発者のtrendingも試した。

def show_trending_developers(since="daily",language=None):
    result=get_trending_developers(since=since,language=language)
    for i,developer in enumerate(result):
        print(i,"番目")
        print("ユーザー名:",developer["username"])
        print()

        with open("test.json","w",encoding="utf-8") as f:
            json.dump(developer,f,indent=4)


if __name__=="__main__":
    show_trending_developers(since="daily",language="javascript")

結果は25個あったので省略。 きちんと現在のtrendingと同じであった。

まとめ

Github の公式APIには、Trendingを取得するものがなかった。 そこで、非公式のTrendingを取得するAPIGithub Unofficial Trending APIを使ってTrendingを取得してみた。 使い方も簡単で、きちんと動作していた。

Github API v4を使ってみる(1)動かしてみる

はじめに

Githubの情報(リポジトリやスター数など)をコードから取得できる、Github APIがある。 Github APIのバージョンにはv3とv4がある。v3はREST APIで、v4はGraphQLというものをベースにしたAPIとなっている。

GraphQLベースのv4を2種類の方法で使ってみる。

使ってみる

使い方としては、2つの方法があり、

  1. GraphQL Explorer
  2. アクセストークンを使う方法

を使うことで、APIを呼び出す。GraphQL Explorerは、ブラウザーで使えて、入力補完もしてくれるので使いやすい。

1. GraphQL Explorerを使う方法

GraphQL Explorerは、 Github APIをブラウザーで使えるようにしたもの。 GraphiQLをベースにしてある。 GraphQL Explorerは、queryのみ使え、読み込みのみ出来る。

まず、https://developer.github.com/v4/explorer/ でGraphQL Explorerのページを開く。

f:id:msdd:20200423112547p:plain
GraphQL Explorer

右上のsign inでgithubにサインインする。

f:id:msdd:20200423112647p:plain
sign in

権限の許可のページが出てくる。 権限は、下のものへのアクセスの許可が求められている。

user
public_repo
repo
repo_deployment
repo:status
read:repo_hook
read:org
read:public_key
read:gpg_key

緑のAuthorize githubと書いてあるボタンを押して、許可する。

f:id:msdd:20200423112835p:plain
権限許可

explorerのページへ飛び、explorerが使えるようになる。これで、github apiを使うことが出来る。

f:id:msdd:20200423112855p:plain
explorer画面

最初に書かれているクエリで試してみる。

query { 
  viewer { 
    login
  }
}

丸い実行ボタンを押すことで、実行される。

f:id:msdd:20200423112920p:plain

結果は右側の部分に表示される。

{
  "data": {
    "viewer": {
      "login": "3sdd"
    }
  }
}

これで、explorerを使ってGithub APIを使えるようになった。 jsonファイルにしたければ、適当なファイル名(data.jsonなど)を付けて、結果を保存する。

GraphiQLでも、設定することで使うことができるようである。
https://developer.github.com/v4/guides/using-the-explorer/#using-graphiql

2. アクセストークンを使う方法

アクセストークンを使って、リクエストで取得することもできる。

githubのガイドではcurlが使われていた。 今回は、pythonで試してみる。

コマンドで使うには、アクセストークン(personal access token)が必要となる。 githubでアクセストークンを作る。 このページにアクセストークンの 作り方が書いてある。画像の通り進めていく。 スコープについての設定は、上のexplorerと同様のものにしておく。

user
public_repo
repo
repo_deployment
repo:status
read:repo_hook
read:org
read:public_key
read:gpg_key

作成すると、下の画像のように黒塗りの部分にアクセストークンが生成されるので、保存しておく。

f:id:msdd:20200423112941p:plain
personal access token

次にアクセストークンを使って、Github APIを呼び出す。 今回は、pythonのバージョンは3.7.4で実行した。 先ほど、作ったアクセストークンをヘッダーにセットしてプログラムからアクセスできるようにする。valuesの値部分のquery{...}の部分は求めたいクエリにする。今回は、ログイン者の情報を求める。

import urllib.request
import json


personal_access_token="アクセストークン記入" # 自分のアクセストークン
endpoint="https://api.github.com/graphql" #送信先のendpoint
headers={"Authorization":f"Bearer {personal_access_token}"}

values={"query":"query{viewer{login}}"} # query部分 値に内容を書く
data=json.dumps(values)   #strにする
data=data.encode("utf-8") #byte型へ変換する

#リクエストを作成送信して、結果を得る。
req=urllib.request.Request(endpoint,headers=headers,data=data)
with urllib.request.urlopen(req) as response:
    body=response.read()

#結果が変数bodyに入っているのでjson形式で整形して
# data.jsonというファイルを作成し保存する
j=json.loads(body)
with open("data.json","w",encoding="utf-8") as f:
    json.dump(j,f,indent=4)

実行してみると、 data.jsonというファイルが出来て、 中にはクエリで求めた情報、 ログインユーザーの情報が入っている。

{
    "data": {
        "viewer": {
            "login": "3sdd"
        }
    }
}

おわりに

Github API v4をGraphQL Explorerとアクセストークンを使う2種類の方法で試してみた。 GraphQL Explorerは、補完もあり手軽に試せる。プログラムから使いたいときは、アクセストークンを作って使うことが出来る。

次は、クエリの内容について調べたい。

参考サイト

https://developer.github.com/v4/guides/forming-calls/

PyTorchでの学習の進み具合をプログレスバーで表示してみる

はじめに

前回、tqdmを使って、プログレスバーを表示してみた。

msdd.hatenablog.com

今回は、実際にpytorchを使って、ニューラルネットワークを学習する時の学習の進行度を プログレスバーで表示してみる。

学習時にバーを表示させる

pytorchを使って、ニューラルネットワークを学習させる時に、 プログレスバーを表示させて、学習状況を見やすくしてみる。

バージョン

import tqdm
import torch
import torchvision

print(tqdm.__version__)
print(torch.__version__)
print(torchvision.__version__)

出力

4.43.0
1.4.0
0.5.0

tqdmのバージョンを4.38.0?では、 学習の途中で無限ループのような感じになった。 なので、tqdmのバージョンを上げて、4.43.0で実行すると、 きちんと動くようになった。

import文とパラメータ

import文

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms

from tqdm import tqdm

パラメータの変数を宣言する。gpuありで学習した。 クラス数は、今回用いたデータセットCIFAR10の10クラス。epoch数は、 学習状況を少しみたいだけなので、少なくした。 batch sizeとlrは適当にした。

device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
batch_size=128
num_classes=10
num_epochs=20
lr=0.1

データセットの用意

データセットの用意をする。 今回は、画像識別のデータセットのCIFAR10を用いた。 データセットを読み込み時に変更するtransformは、画像をtensor形式にするToTensor()のみにした。 CIFAR10()でデータをダウンロードし、使えるようにする。 その後、データをまとめて処理できるようにbatch_size分を読み出すdataloaderを作り、データの準備は終わり。

transform={
    "train":transforms.Compose([
        transforms.ToTensor(),
    ]),
    "test":transforms.Compose([
        transforms.ToTensor(),
    ])
}

dataset={
    "train":torchvision.datasets.CIFAR10(root="CIFAR10",train=True,transform=transform["train"],download=True),
    "test":torchvision.datasets.CIFAR10(root="CIFAR10",train=False,transform=transform["test"],download=True)
}

dataloader={
    "train":torch.utils.data.DataLoader(dataset["train"],batch_size=batch_size,shuffle=True,num_workers=2),
    "test":torch.utils.data.DataLoader(dataset["test"],batch_size=batch_size,shuffle=False,num_workers=2)
}

モデル、optimizer、ロス関数を用意

ネットワークはresnet18を使用した。最後の出力の数を、データセットのクラス数の10クラスへと 変えて、前に宣言したdeviceへ送る。optimizerはadamでロス関数はcross entropy lossを使用した。

model=torchvision.models.resnet18(pretrained=False)
model.fc=nn.Linear(model.fc.in_features,num_classes)
model=model.to(device)

optimizer=optim.Adam(model.parameters(),lr=lr)
criterion=nn.CrossEntropyLoss()

学習でプログレスバー表示

学習部分のコード。 1epochごとに、学習とテストを行う。 データローダーでバッチごとに処理をしていくたびにプログレスバーを更新していくように している。

tqdm()でバーを表示できるようにする。今回は手動でバーを更新する方法を使った。 引数のtotalに繰り返しの合計数などを入れ、繰り返し時にupdate(n)でバーをnだけ進めて行く。 今回はdataloaderのサイズ、バッチごとに処理する回数を入れた。 引数のunitは入れた文字で処理速度の単位を変えることができる。今回はバッチごとの処理をしているので、batchを入れた。\ set_description(f"Epoch[{epoch}/{num_epochs}]({phase})")で、 バーの前側の部分に現在のepoch数と最終のepoch数、 trainかtestかを表示するようにしている。文字列の前にfを付けることで、{}で囲んだ中に変数を入れることで、 変数の値を表示できて便利である。\ set_postfix({"loss":running_loss.item(),"accuracy":accuracy })でバッチごとに学習またはテストした途中の段階でのlossと精度accuracyを更新している。\ 最後に、バッチ1つ分の処理が終了したので、update(1)でバーを1進め、totalになるまで繰り返す。

def train(model,dataloader,otpimizer,criterion,num_epochs,device):

    for epoch in range(1,num_epochs+1):

        for phase in ["train","test"]:

            if phase=="train":
                model.train()
            elif phase=="test":
                model.eval()

            with torch.set_grad_enabled(phase=="train"):
                loss_sum=0
                corrects=0
                total=0

                with tqdm(total=len(dataloader[phase]),unit="batch") as pbar:
                    pbar.set_description(f"Epoch[{epoch}/{num_epochs}]({phase})")
                    for imgs,labels in dataloader[phase]:   
                        imgs,labels=imgs.to(device),labels.to(device)
                        output=model(imgs)
                        loss=criterion(output,labels)

                        if phase=="train":
                            optimizer.zero_grad()
                            loss.backward()
                            optimizer.step()

                        predicted=torch.argmax(output,dim=1) ## dimあってる?
                        corrects+=(predicted==labels).sum()
                        total+=imgs.size(0)
                            
                        #loss関数で通してでてきたlossはCrossEntropyLossのreduction="mean"なので平均
                        #batch sizeをかけることで、batch全体での合計を今までのloss_sumに足し合わせる
                        loss_sum+=loss*imgs.size(0) 

                        accuracy=corrects.item()/total
                        running_loss=loss_sum/total
                        pbar.set_postfix({"loss":running_loss.item(),"accuracy":accuracy })
                        pbar.update(1)

学習を行うと、バーが表示され、lossとaccuracyが更新されているのがわかる。 進むにつれてlossが下がっていっており、 学習が進んでいるとわかる。予測時間も表示されるので、どれくらいで終わるかも予測しやすい。

train(model,dataloader,optimizer,criterion,num_epochs,device)

f:id:msdd:20200326180450g:plain

まとめ

実際の学習コードでプログレスバーを表示して、学習の進み具合を見れるようにしてみた。 実際にlossが下がっているかを確認でき学習の状況を把握しやすくなる。

ニューラルネットワーク学習時に進み具合を表示する簡単な方法

はじめに

ニューラルネットワークの学習で、進行度合を文字ではなく、 プログレスバーのような目で見える形にするとわかりやすい。 tqdmというライブラリを使い、学習経過をプログレスバーで簡単に表示できるので試してみる。

tqdmとは

簡単に、コマンドライン上にプログレスバーを表示でき、 経過時間と予測時間も表示してくれる便利なpythonライブラリ。

f:id:msdd:20200324215316g:plain

インストール方法

pip install tqdm

プログレスバーの表示

使用ライブラリとバージョン

プログレスバー表示

  • tqdm : 4.38.0

試してみる

ニューラルネットワークの学習の時のことを考えて、試してみる。

datasetで1epoch分、つまりdatasetの数(下の例ではデータ6個分を学習するのを1epoch)だけ学習するとする。 下のコードのようにdatasetがあり、データを読み込み、 time.sleep()部分が学習部分とする。 このままだと、何も表示されず学習の結果がわかりにくい。 そこで、プログレスバーを表示して、学習状況を見やすくしてみる。

import time

dataset=["a","b","c","d","e","f"]

for data in dataset:

    time.sleep(0.5)

出力




上のコードに少し足すだけで、プログレスバーを表示できる。for文でのデータセットの部分をtqdmでおおう。 tqdmの引数は、iterable(反復可能)なもので、 range()などを引数として入れられる。 これだけで、簡単にバーを表示できる。

表示は、進行度の%、処理した数と処理すべき合計数、経過時間、残り時間の予測、処理スピードなどの情報が表示される。

import time
from tqdm import tqdm

dataset=["a","b","c","d","e","f"]

for data in tqdm(dataset):

    time.sleep(0.5)

出力

f:id:msdd:20200324215316g:plain

手動でプログレスバーを操作する方法もある。 tqdmの引数のtotalに合計値を入れ、tqdmのインスタンスpbarとする。 手動でupdate(n)を使い、nだけ増やしていく。

dataset=["a","b","c","d","e","f"]

with tqdm(total=len(dataset)) as pbar:
    for data in dataset:
        time.sleep(0.5)

        pbar.update(1)

出力

f:id:msdd:20200324215403g:plain

学習の時、現在の精度やlossなどの値も確認したいので、表示してみる。 情報を表示するには、set_postfix(dict)を用いることで表示できる。 引数には、キーが表示したい名前、辞書の値が表示したい値の辞書をいれる。

dataset=["a","b","c","d","e","f"]

accuracy=0
with tqdm(total=len(dataset)) as pbar:
    for data in dataset:
        time.sleep(0.5)
        accuracy=accuracy+10
        pbar.set_postfix({"accuracy":accuracy})
        pbar.update(1)

出力

f:id:msdd:20200324215424g:plain

また、学習には1epoch分だけでなく、何epochも行う。 これをバーで表示すると、何個もバーが連なり、 現在が何epoch目かがわからないようになる。

dataset=["a","b","c","d","e","f"]

num_epochs=10
for epoch in range(1,num_epochs+1):
    #1epoch分の学習
    with tqdm(total=len(dataset)) as pbar:
        for data in dataset:
            time.sleep(0.5)

            pbar.update(1)

出力

f:id:msdd:20200324215443g:plain

そこで、前側にepoch数を書いておくことで、現在の進行度がわかるようになる。 バーの前側に文字を表示するのは、set_description()

dataset=["a","b","c","d","e","f"]

num_epochs=10
for epoch in range(1,num_epochs+1):
    #1epoch分の学習
    with tqdm(total=len(dataset)) as pbar:
        for data in dataset:
            time.sleep(0.5)

            pbar.set_description(f"Epoch[{epoch}/{num_epochs}]")
            pbar.update(1)

出力

f:id:msdd:20200324215520g:plain

まとめ

学習の過程を見てわかりやすいように、プログレスバーで表示してみた。 データセットなどのiterableな部分をtqdmで包むだけで、 簡単に表示でき使いやすい。 ニューラルネットワークの学習にも使っていきたい。

Google Colab Proを使いたかった(使えなかった)

はじめに

Google ColabでGPU付でニューラルネットワークの学習を行っていたら、 約6時間ぐらいの所でプログラムが止まってしまった。 その後、再実行使用とすると下のようなGPUバックエンドに接続できませんとの表示が出た。

f:id:msdd:20200323164811p:plain

学習には時間がかかるので、6時間で止まってしまうとつらい。もう少し長く使えないかと探していると Google Colab Proがあることを知った。Google Colabは無料で使えるが、Pro版は、1カ月9.99ドル払うことで、より速いGPUに優先的にアクセス、より長時間、より多くのメモリを使用可能となるとある。

Google Colab Pro使いたかった

Google Colab Proの特徴として下の3つがある。

  • GPU
    • Colab ProはT4やP100など高性能なGPUを利用しやすい(使用上限は有り)。使用量の上限もProの方が多い。
  • 接続時間最大
    • 無料版: 12時間
    • Pro : 24時間
  • メモリ
    • Pro: ハイメモリVMを使用可能(無料版の2倍のメモリとCPU)

月9.99ドルの低価格なのでリソース保障はなし。

これはすごく便利そうと思い、早速申し込もうとするが、 現在はColab Proは米国のみで利用可能であり、日本では使えないことがわかる。 書いてあるだけで、実際は使えるのではと思い、 試しに購入できないかと購入画面へと行くと、 どうやら米国の住所がいるようである。

f:id:msdd:20200323164824p:plain

残念。。。 米国の住所なんてないのであきらめた。 よさそうなプランなので使いたかった。

おわりに

stack overflow での回答で、他のGoogleのサービス同様に、最初に米国でローンチした後、他国への展開するかもしれないとある。 それを期待して、日本でもサービス開始するのを待ちたい。

PyTorchでOne Hotに変換する

はじめに

PyTorchでOneHotに変換する方法がわからなかったので調べた。

変換方法

One Hotに変換するには、torch.nn.functionalにあるone_hot()という 関数を使うことで変換できる。 PyTorchのバージョンは、v1.1.0以降が必要。 引数に、入力に変換前のtensorと、num_classesにクラス数を 入る。

torch.nn.functional.one_hot(tensor, num_classes=-1)

ドキュメントをここです。

使い方サンプル

まず、必要なモジュールをインポートと、バージョンを確認しておく。

import torch
import torch.nn.functional as F

print(torch.__version__) 

バージョンが表示され、1.4.0であった。 1.1.0以降なので、使える。

one_hotを使ってみる。 入力として、labelを用意した。 これは、値が3のみ入っているtensorである。この3をone hot表現へと変換してみる。 この入力のlabelとクラス数(今回は、10クラスとしてみる)を one_hot関数の引数として入れることで、結果としてone hot表現が得られる。

label=torch.tensor(3)
print("label:",label)

one_hot=F.one_hot(label,num_classes=10)
print("one hot:",one_hot)

結果は下のようになった。 元の3が、変換後は[0,0 ...0]と10クラス分の0が並んだものをindexが3の部分のみ1にした ものへと変換できている。

label: tensor(3)
one hot: tensor([0, 0, 0, 1, 0, 0, 0, 0, 0, 0])

このone_hot関数は、引数のnum_classesを省略することもできる。 省略した時の挙動を見てみる。 先ほどと同じく、labelを設定。 それを、次は、num_classesの引数を省いたone_hot関数に入れてみる。

label=torch.tensor(3)
print("label:",label)

one_hot=F.one_hot(label)
print("one hot:",one_hot)

出力は下のようになった。 こちらも、ラベルの3からone hotへと変換できている。 しかし、num_classesを指定した時とは違って、 出力されたtensorのサイズが4になっている。 これは、one_hot関数の引数のnum_classesに何も指定しないと入力の中で一番大きい値からクラス数が決定されるためである。 今回は、最大が3なので0から始めて3番目が1になるように、クラス数は4となり、one hotでは4つの値となっている。

label: tensor(3)
one hot: tensor([0, 0, 0, 1])

このone_hot関数は、複数の値にも対応している。 先ほどまでの、labelの値は3という1つの値のみだったが、 今回は、0,1,2,0,4という6つの値に適用してみる。 変更したのは、入力のtensorを複数に変更しただけである。

label=torch.tensor([0,1,2,0,4])
print("label:",label)

one_hot=F.one_hot(label,num_classes=5)
print("one hot:",one_hot)

結果は、下のようになった。 出力も複数出力されていて、それぞれのone hot表現を取得できている。

label: tensor([0, 1, 2, 0, 4])
one hot: tensor([[1, 0, 0, 0, 0],
        [0, 1, 0, 0, 0],
        [0, 0, 1, 0, 0],
        [1, 0, 0, 0, 0],
        [0, 0, 0, 0, 1]])

まとめ

PyTorchでOne Hotに変換する方法を調べた。 すでに、PyTorchで用意されていて、torch.nn.functionalを使うことで、 変換できる。使う時には、きちんと、引数にクラス数を指定しないと 入力の内容から自動でクラス数が推定されて結果がおかしくなると思うので 注意が必要。

imgaugでbounding boxを表示

はじめに

物体検出では、物体の位置を表示する方法として、検出した物体を矩形(bounding box、バウンディングボックス)で囲む方法がある。 そのbounding boxを表示する方法として、imgaugというライブラリを使って表示することができるので試してみた。

imgaugとは

画像拡張用のpythonライブラリ

画像にノイズを加えたり、クロップしたりなどできる。 その中にbounding box表示用の関数もある。

リンク

bounding boxの表示

使用ライブラリのバージョン

  • torchvision: 0.5.0
  • imgaug: 0.4.0

データの用意

画像とbounding boxの位置の情報を用意する。今回は、torchvisionのクラスを使い、Pascal VOCのデータセットを使用した。 Pascal VOCでは、bounding boxは左上の頂点と右下の頂点の座標で表されている。

画像(image)とアノテーション情報(annotation)を取得した。 アノテーション情報の中に、複数の物体の情報がはいっており、物体の名前とbounding boxの位置が含まれている。

import torchvision

voc_dataset=torchvision.datasets.VOCDetection(root="VOCDetection/2012",year="2012",image_set="train",download=True)

image,target=voc_dataset[0]
annotation=target["annotation"]

画像を表示してみる。

from IPython.display import display

display(image)

f:id:msdd:20200317085206p:plain

物体の情報を表示してみる。

print(annotation["object"])
[{'name': 'horse', 'pose': 'Left', 'truncated': '0', 'occluded': '1', 'bndbox': {'xmin': '53', 'ymin': '87', 'xmax': '471', 'ymax': '420'}, 'difficult': '0'}, {'name': 'person', 'pose': 'Unspecified', 'truncated': '1', 'occluded': '0', 'bndbox': {'xmin': '158', 'ymin': '44', 'xmax': '289', 'ymax': '167'}, 'difficult': '0'}]

2つの物体(horseとperson)があり、それぞれbounding boxの情報(bndbox)をもっている。bounding boxの情報は左上の頂点の座標のxminymin、右下の頂点の座標のxmaxymaxで表されている。

このbounding boxを画像上に表示してみる。

bounding boxの描画

アノテーション情報から物体の名前(obj["name"])、bounding boxの位置情報(xminyminxmaxymax)を取り出す。 bb=BoundingBox(x1=xmin,y1=ymin,x2=xmax,y2=ymax,label=obj["name"])で描画用のbounding boxを作成する。 引数はx1,y1,x2,y2で左上の座標(x1,y1)と右下の座標(x2,y2)を指定する。 引数のlabelに表示したい名前を入れることもできる。 それをlist形式で、bb_listに保存する。 BoundingBoxesOnImage()で複数のbounding boxをまとめたものを作り、draw_on_image()で画像に対してbounding boxを付け加えた画像を生成する。 BoundingBoxesOnImage()の引数のshapeでは、(height,width,channel)の順の画像のshapeを入力する。 draw_on_image()での返り値は、ndarrayの画像なので表示するためにPILの画像に変換して表示した。

from imgaug.augmentables.bbs import BoundingBox,BoundingBoxesOnImage
from PIL import Image

bb_list=[]
for obj in annotation["object"]:
    box=obj["bndbox"]
    xmin,ymin=float(box["xmin"]),float(box["ymin"])
    xmax,ymax=float(box["xmax"]),float(box["ymax"])

    #bounding boxを作成
    bb=BoundingBox(x1=xmin,y1=ymin,x2=xmax,y2=ymax,label=obj["name"])
    bb_list.append(bb)

image_shape=(image.height,image.width,3)

#imageにbounding boxを描画
bbs=BoundingBoxesOnImage(bb_list,shape=image_shape)
bbs_image=bbs.draw_on_image(image,color=(255,255,255))

# 表示するためにpil形式に変換して、表示する
pil_bbs_image=Image.fromarray(bbs_image)
display(pil_bbs_image)

得られた画像はきちんと、物体の周囲にbounding boxが表示され、その上側に設定した名前が表示されている。

f:id:msdd:20200317085105p:plain

おわりに

imgaugを使ってbounding boxを表示してみた。 ラベル付きでbounding boxを表示できる関数が手軽に使えて便利。物体ごとに色を変えたいので、 できる方法がないか調べていきたい。

参考

TorchvisionでIoUの計算

はじめに

物体検出の評価指標で使われたりしているIoUを求めてみた。

IoUとは

Intersection over Unionの略である。Jaccard indexとも言われている。

物体検出の評価指標などで使われている。 物体検出では、物体を矩形で囲むことで、物体の位置を予測する。 正解もまた、矩形で囲まれたものが与えられる。この予測した矩形と正解の矩形を 評価する手法の1つとして、IoUが使われる。

2つの矩形があり、重なった部分の面積を面積の和で割ったものがIoUである。

f:id:msdd:20200310171456p:plain

2つの矩形が、まったく重ならなかったらIoU=0となり、完全に重なるとIoU=1となる。

f:id:msdd:20200310151355p:plainf:id:msdd:20200310151319p:plain

IoUを求めてみる

IoUを求めてみる。

バージョン

  • PyTorch : 1.4.0
  • Torchvision : 0.5.0

使った関数

torchvision.ops.boxes.box_iou(boxes1,boxes2)

torchvisionにはtorchvision.ops.boxesbox_iou(boxes1, boxes2)という関数がある。ドキュメントには載っていないが使えるので使う。

引数が矩形のboxes1とboxes2でそれぞれサイズがTensor[N, 4]、Tensor[M, 4]である。 この4の所は矩形の座標で(x1,y1,x2,y2)である。 返り値はiouでサイズがTensor[N, M]のテンソルである。

Google Colabで試した

矩形を2つ用意する。 最も左上の座標を(0,0)として右方向、下方向が+となる方向。 それぞれ左上の座標と左下の座標で矩形を表している。

一方の矩形にもう一方の矩形が含まれている例

box1は(0,0),(3,3)の矩形(黄色)、box2は(0,0),(5,5)の矩形(青)。

f:id:msdd:20200310163853p:plain:w400

import torchvision.ops.boxes as bops

box1=torch.tensor([[0,0,3,3]],dtype=torch.float)
box2=torch.tensor([[0,0,5,5]],dtype=torch.float)
print("box1:",box1)
print("box2:",box2)

area1,area2=bops.box_area(box1),bops.box_area(box2)
print("area1:",area1)
print("area2:",area2)

iou=bops.box_iou(box1,box2)
print(iou)

実行してみる。

box1: tensor([[0., 0., 3., 3.]])
box2: tensor([[0., 0., 5., 5.]])
area1: tensor([9.])
area2: tensor([25.])
tensor([[0.3600]])

重なり部分の面積=(3-0)(3-0)=9、和の部分の面積=(5-0)(5-0)=25となる。

f:id:msdd:20200310164145p:plain:w250
重なり部分
f:id:msdd:20200310164140p:plain:w250
和の部分

その結果、  IoU=\frac{9}{25}=0.36 となる

2つの矩形が重ならない例

box1:(5,0),(8,3)で、box:(0,0),(5,5)の場合

f:id:msdd:20200310165051p:plain:w400

box1=torch.tensor([[5,0,8,3]],dtype=torch.float)
box2=torch.tensor([[0,0,5,5]],dtype=torch.float)
print("box1:",box1)
print("box2:",box2)

iou=bops.box_iou(box1,box2)
print(iou)

実行してみる。

box1: tensor([[5., 0., 8., 3.]])
box2: tensor([[0., 0., 5., 5.]])
tensor([[0.]])

重なっている部分がないので、IoU=0となる。

一部が重なる例

box1:(3,1),(6,4)で、box:(0,0),(5,5)の場合

f:id:msdd:20200310165157p:plain:w400

box1=torch.tensor([[3,1,6,4]],dtype=torch.float)
box2=torch.tensor([[0,0,5,5]],dtype=torch.float)
print("box1:",box1)
print("box2:",box2)

iou=bops.box_iou(box1,box2)
print(iou[0][0].item())
print(iou)

実行してみる。

box1: tensor([[3., 1., 6., 4.]])
box2: tensor([[0., 0., 5., 5.]])
0.2142857164144516
tensor([[0.2143]])

重なり部分の面積は6、和の部分の面積は28なので、 IoU=\frac{6}{28}=0.214285...=0.2143 となる。

f:id:msdd:20200310165216p:plain:w250
重なり部分
f:id:msdd:20200310165213p:plain:w250
和の部分

入力のboxesが複数ある例

boxes1:(3,1),(6,4)の黄色の矩形と(1,3),(4,5)の紫の矩形 で、boxes2:(0,3),(2,6)の赤の矩形と(0,0),(5,5)の青の矩形の場合

f:id:msdd:20200310165540p:plain:w400

boxes1=torch.tensor([[3,1,6,4],[1,3,4,5]],dtype=torch.float) #黄, 紫
boxes2=torch.tensor([[0,3,2,6],[0,0,5,5]],dtype=torch.float) #赤, 青

print("boxes1:",boxes1)
print("boxes2:",boxes2)

iou=bops.box_iou(boxes1,boxes2)
print(iou)

出力は、

boxes1: tensor([[3., 1., 6., 4.], [1., 3., 4., 5.]])
boxes2: tensor([[0., 3., 2., 6.], [0., 0., 5., 5.]])
tensor([[0.0000, 0.2143],[0.2000, 0.2400]])

返り値のIoUは入力サイズのboxes1が2、boxes2が2より、2x2のtensorが返ってくる。

得られたIoUのtensorは下の表のようになる。縦がboxes1で横がboxes2の並びとなる。

(0,3),(2,6) (0,0),(5,5)
(3,1),(6,4) 黄色 0.0 0.2143
(1,3),(4,5)  0.2000 0.2400

f:id:msdd:20200310165557p:plain

つまり、iou[index1][index2]でboxes1[index1]とboxes2[index2]の2つの矩形のIoUを得られる。

つまった点

box_iouの入力のbox1,box2をdtype=torch.intのtensorで計算すると、結果が0になってしまう。 そのため、dtypeはfloatにする必要がある。

まとめ

IoUについて調べ、torchvisionのbox_iou関数を使ってIoUを求めてみた。 複数の矩形のIoUを一括で求められて便利。 今後は、物体検出の評価指標は他にも色々あるので調べてみたい。

参考サイト

PyTorchでPascal VOCデータセット を使ってみた

はじめに

Object DetectionのデータセットPascal VOCデータセットをPyTorchで使ってみた。

Pascal VOCとは

Pattern Analysis, Statistical Modelling and Computational Learning(PASCAL) Visual Object Classes Challenge(VOC)。 2005年~2012年に開催されていた物体のクラス認識などのコンペティション。 物体検出やセグメンテーションなどのタスクがある。

f:id:msdd:20200307184142j:plain
物体検出サンプル(http://host.robots.ox.ac.uk/pascal/VOC/voc2012/index.html より)

f:id:msdd:20200307184315j:plainf:id:msdd:20200307184358p:plain
セグメンテーションのサンプル(http://host.robots.ox.ac.uk/pascal/VOC/voc2012/index.html より)

クラス数は2007年からは20クラス。

  • Person: person
  • Animal: bird, cat, cow, dog, horse, sheep
  • Vehicle: aeroplane, bicycle, boat, bus, car, motorbike, train
  • Indoor: bottle, chair, dining table, potted plant, sofa, tv/monitor

その中で今回は、物体検出のデータセットを使ってみた。

VOCDetectionクラス

PyTorchでPascal VOCのデータセットを使うのに、 Torchvisionの VOCDetection というクラスがある。 Pascal VOCのデータを簡単に読み込めるようなクラスが用意されている。 これを使ってみた。

クラスは下のような引数をとる。

torchvision.datasets.VOCDetection(root, year='2012', image_set='train', download=False, transform=None, target_transform=None, transforms=None)

torch.utils.data.Datasetのサブクラスで、 データを得る__getitem__と、 データセットの大きさを得る __len__を持っている。

引数は下のようなもの。

引数名 説明
root データセットのルートフォルダー (e.g.) ./VOCDetection
year どの年のデータセットか 2007, 2008, 2009, 2010, 2011, 2012
image_set データセットの区分 train, trainval, val
download データセットをダウンロードするかどうか True, False
transform 画像に対して変形を行う際のtransform関数 None(何もしない), torchvision.transforms.ToTensor()など
target_transform targetに対して変形を行う際のtransform関数 None(何もしない) など
transforms transformとtarget_transformを合わせたようなやつ。?なんで必要なのこれ? None(何もしない)など

year=2007の時のみ、image_set="test"も使える。

indexでアクセスできる。 返り値は、(image,target)で、imageはPILの画像、targetはxml treeの辞書となっている。 targetには画像サイズやbounding boxの位置などの情報が入っている。

試してみた

バージョン

PyTorch: 1.4.0
Torchvision : 0.5.0

Google Colabで実行

まずは、データセットクラスを宣言。2012年のデータセットのtrainを使う。 download=Trueなので、実行するとデータがrootで指定した./VOCDetection/2012にダウンロードされる。

import torchvision

voc_dataset=torchvision.datasets.VOCDetection(root="./VOCDetection/2012",year="2012",image_set="train",download=True)

データセットから一番最初のデータを取得する。 返り値は、画像とアノテーション情報である。

image,target=voc_dataset[0]

データセットの画像を表示してみる。

from IPython.display import display

display(image)

f:id:msdd:20200307184649p:plain

次に、アノテーション情報targetについて見てみる。 targetを表示してみると、下のような辞書の形でアノテーション情報が入っている。

{'annotation': {'folder': 'VOC2012', 'filename': '2008_000008.jpg', 'source': {'database': 'The VOC2008 Database', 'annotation': 'PASCAL VOC2008', 'image': 'flickr'}, 'size': {'width': '500', 'height': '442', 'depth': '3'}, 'segmented': '0', 'object': [{'name': 'horse', 'pose': 'Left', 'truncated': '0', 'occluded': '1', 'bndbox': {'xmin': '53', 'ymin': '87', 'xmax': '471', 'ymax': '420'}, 'difficult': '0'}, {'name': 'person', 'pose': 'Unspecified', 'truncated': '1', 'occluded': '0', 'bndbox': {'xmin': '158', 'ymin': '44', 'xmax': '289', 'ymax': '167'}, 'difficult': '0'}]}}

画像のサイズ、物体の名前と位置などを使うので取り出してみる。 bounding boxの情報は、画像の一番左上のピクセルを(1,1)とした座標であり、左上の頂点と右下の頂点の情報で保持されている。

annotation=target["annotation"]

#画像サイズ
width, height, depth = annotation["size"].values()
#物体の情報
objects=annotation["object"]
for obj in objects:
    name=obj["name"]
    bndbox=obj["bndbox"]
    #bounding boxの左上と右下の座標
    x_min,y_min,x_max,y_max=[int(val) for val in bndbox.values()]

画像とbounding boxが得られたので、画像上にbounding boxを表示してみる。 bounding boxの適用した画像を作る関数をつくる。もしかしたら、すでに便利な関数があるかもしれないが、 少し調べた範囲ではわからなかった。

from PIL import Image,ImageDraw

def make_bbox_img(pil_image,x0y0,x1y1,text,width=1,color="red"):
    bbox_img=pil_image.copy()
    d=ImageDraw.Draw(bbox_img)
    d.rectangle((x0y0,x1y1),outline=color,width=width)
    #テキストのライン
    d.text((x0y0[0]+1,x0y0[1]+1),text,fill="white")
    d.text((x0y0[0]-1,x0y0[1]-1),text,fill="white")
    #テキスト
    d.text(x0y0,text,fill="black")

    return bbox_img


name_to_color={
    "person":"orange",
    "bird":"darkcyan",
    "cat":"lightyellow",
    "cow":"coral",
    "dog":"dimgray",
    "horse":"darkgreen",
    "sheep":"wheat",
    "aeroplane":"red",
    "bicycle":"royalblue",
    "boat":"blue",
    "bus":"yellow",
    "car":"hotpink",
    "motorbike":"lawngreen",
    "train":"cyan",
    "bottle":"olive",
    "chair":"indigo",
    "diningtable":"maroon",
    "pottedplant":"mediumslateblue",
    "sofa":"purple",
    "tvmonitor":"lime",
}


def make_voc_bbox_img(pil_image,objects,width=1):
    bbox_img=pil_image.copy()
    d=ImageDraw.Draw(bbox_img)

    #list形式でないものがあるのでlist形式へ直す
    if not isinstance(objects,list):
        objects=[objects]

    for obj in objects:
        name=obj["name"]
        bndbox=obj["bndbox"]
        x_min,y_min,x_max,y_max=[int(val) for val in bndbox.values()]

        color=name_to_color[name]
        #x:[1,image_width],y:[1,image_height]
        bbox_img=make_bbox_img(bbox_img,(x_min-1,y_min-1),(x_max-1,y_max-1),text=name,color=color,width=width)

    return bbox_img

先ほどの画像に適用してみる。

bbox_img=make_voc_bbox_img(image,objects)
display(bbox_img)

f:id:msdd:20200307184744p:plain

うまくbounding boxを表示出来ている。 色が見にくいなどはあるが、表示できるようになったので、まあよしとする。 bounding box表示方法は、簡単な方法がないか探していきたい。

数枚の画像で試してみた。 f:id:msdd:20200307184810p:plain

f:id:msdd:20200307184827p:plain

f:id:msdd:20200307184833p:plain

つまづいた点

つまづいた点としては下のものがある。
- annotationのbounding boxの座標の値がstr型 - annotationのobjectの値が、全てlist形式と思っていたが、 objectが複数あるものはlist形式で、単一のものはlist形式でない点。

まとめ

PyTorchで物体検出を試してみたくて、Pascal VOCデータセットについて調べて動かしてみた。 データ形式などがわかった。 すでにクラスが用意されているので、自分でデータセットの読み込みなどを書く必要がなくて便利。 bounding box表示の関数がないか今後調べていきたい。

TensorBoardにおけるデータのSmoothingのアルゴリズム

はじめに

tensorboardでスカラー表示のグラフのところに、smoothingを変更するスライダーがある。 名前的に滑らかさを調節するスライダーということはわかるが、どんなアルゴリズムで滑らかにしてるんだろうと思ったので調べた。

f:id:msdd:20191229172903p:plain
smoothingスライダー

smoothingのアルゴリズム

stack overflowでの回答

stack overflowで回答されているのを見つけた。

stackoverflow.com

ここでの回答によれば、exponential moving average(指数移動平均)というものが使われているらしい。

wikipediaによると、

重みは指数関数的に減少するので、最近のデータを重視するとともに古いデータを完全には切り捨てない(重みは完全にゼロにはならない)。

というものらしい。

時刻tでの値をY[t]とする。時刻tでの指数移動平均をS[t]、係数aとすると、

S_[t] = a * Y_[t-1] + (1 - a) * S_[t-1]

で計算できる。参照元のページも見てみると、 0<=a<1とあり、Y[t-1]はY[t]に置き換えたものもあると書いてある。

tensorboardでの実装

tensorboardのリポジトリでsmoothingしてところ探した。 ここのresmoothDatasetで行っているようだ。

コードで試す

[1, 2, 3, 4, 4, 6, 5, 3]の値にsmoothingしてみる。

scalars=[1,2,3,4,4,6,5,3]

tensorboardのsmoothingの値を0.2にした値は、 [1, 1.833, 2.774, 3.756 ,3.951, 5.59, 5.118, 3.424] となった。

stack overflowにあった関数をpythonで書いたもの

def smooth(scalars,weight):
    last=scalars[0]
    smoothed=[]
    for point in scalars:
        smoothed_val=last*weight+(1-weight)*point
        smoothed.append(smoothed_val)
        last=smoothed_val

    return smoothed

weight=0.2で実行してみる。

smoothed=smooth(scalars,0.2)
print(smoothed)

出てきたものは、[1.0, 1.8, 2.7600000000000002, 3.7520000000000002, 3.9504, 5.59008, 5.118016, 3.4236032000000005] 近い値は、出てきているが微妙に違う。

tensorboardのコード見に行って書いたのが下のもの。前のものと違う点は、 最初の値に0を使っている(last=0)のと、debias_weightを計算して、値を保存するときに、smooth_valをdebia_weightで割っているところが違う。 このdebias使う理由は、コードのコメントにかいてある。詳しくはわからないが、偏りが生じるのでそれをなくしているものと捉えている。

def debias_smooth(scalars,weight):
    last=scalars[0]
    last=0
    smoothed=[]
    for i,point in enumerate(scalars,start=1):
        smoothed_val=last*weight+(1-weight)*point
        last=smoothed_val

        debias_weight=1-weight**i
        smoothed_val=smoothed_val/debias_weight
        smoothed.append(smoothed_val)       

    return smoothed

同様にweigh=0.2で実行する。

smoothed=debias_smooth(scalars,0.2)
print(smoothed)

[1.0, 1.8333333333333337, 2.7741935483870974, 3.756410256410257, 3.9513444302176697, 5.590373783922171, 5.118068711279505, 3.423609404440076]となった。 tensorboard上の値と桁を整えれば一致している。

メモ

stack overflowのコードを式に直したら少し違っていたけど、よく見てみると一緒だった。

wikipediaの式では、

S_[t] = a * Y_[t-1] + (1 - a) * S_[t-1]

となっていた。 コードを直すと、

S_[t] = weight * S_[t-1] + (1 - weight) * Y_[t]

となる。wikipediaの式と違っていたのであれっとなった。

式変形してみる。weight=1-aとおく。

S_[t] = (1-a) * S_[t-1] + a * Y_[t]

順番入れ替えて、

S_[t] = a * Y_[t]  + (1-a) * S_[t-1]

あと違う部分は、Y_[t]の部分だけだが、wikipediaの参照元のページを見てみると、Y_[t-1]Y_[t]に置き換えているものもあるということだったので、 これはこのままでもよさそう。ということで、ほぼ一緒になった。

まとめ

tensorboardのsmoothing処理はexponential moving average(指数移動平均)を用いてsmoothing処理が行われていて、 この指数移動平均は、現在の値に重点を置いてsmoothingする方法である。

参考サイト

AI最先端技術を調べられるサイト

紹介

どんなの

Papwers With Code というサイト。現在のAI研究がどんな研究がされているのか見れる。 AI研究の最先端の論文のとコードのリンクがセットでのっている。論文はだいたいarxivというサイトにある論文のリンクが張ってある。arxivはプレプリントを投稿できるサイト。コードはgithubで公開されているもの。コードはない時もあれば、色々な人が再実装していて、複数ある時もある。フレームワークもpytorchやtensorflowなど様々。pytorchでの実装が欲しいけど、tensorflowの実装しかないこともある。

f:id:msdd:20191208194145p:plain

分野ごとのページ

Browse the State-of-the-Art in Machine Learning | Papers With Code

sota(state-of-the-art)は最先端という意味で、色々な分野の最先端の研究を見つけられる。Computer Vision(画像とか動画とか扱っている)やNatural Language Processing(自然言語処理)などの分野にわかれていて、その中にその分野のタスクがある。Computer Visionの分野だと、Image Classification(画像分類) やObject Detection(物体検出)などのタスクがある。

f:id:msdd:20191228005325p:plain
分野ごとに分かれている

タスクページ

そのタスクの中の1つimage classificationのページでを見てみる。image classificationのタスクは、与えられた画像に1つのラベルを割り当てていくタスクで、猫画像なら猫というラベルを付けるタスク。leaderboardにそのタスクごとのデータセットがのっている。image classificationのデータセットは、mnist(0から9までの10クラスのラベルが割り振られた白黒画像)やImageNet(1000クラスのラベルが割り振られた画像)などがあり、それぞれ個別のページがある。

f:id:msdd:20191208200635p:plain

データセットページ

ImageNetというデータセットのページを見てみると、上のグラフに円がいくつもある。この円が提案された手法で、横軸が提案された時期で、縦軸がデータセットでの指標となるもので、今回の場合は精度。一番上の線で結ばれているものがsotaのものの推移。 様々な手法が提案されて、精度が上がっていっていることがわかる。しばらくしてまた見に来ると、新しいものが出てきて、sotaになってたりする。進歩がすごく早い。

f:id:msdd:20191228013708p:plain
ImageNetのページ

上の画像の下部分にあるのが、論文とコードがセットになった表で、精度が高い順番に並んでいる。

個人的に気になった研究

Lipreading

唇から言ってることを読み取って文字にする研究

タチコマが唇から読み取っていたのを思い出した。未来感がある。 どこまで読み取れるのかは、リーダーボードがなかったのでわからない。

Face Generation

存在しない人の顔の画像を作る研究

ほぼ、本物の人と見分けがつかないくらいきれいに作れてるのがすごい。 アニメ画像作りたいので、このへんの勉強しなくちゃ。

Code Generation

コード生成の研究

pix2codeのデモが面白かった。 UIのスクリーンショット画像から、htmlを作っていた。 www.youtube.com

おわりに

色々な分野の研究がまとめられているので、どんな研究がなされているのかをぱっと知るのに役に立つ。 論文とコードのリンクもセットでついてるのでとても便利。 また、タスクごとのデータセット調べたりする使い方もできる。

Safebooruからアニメ画像ダウンロード

はじめに

GANでアニメ画像生成したい。そのためには、データとして大量のアニメ画像を集める必要がある。 スクレイピングするのと、動画のSSとる方法があるが、今回は、楽そうな方のスクレイピングで画像集めることにした。

前にアニメデータセット探した中で、sabebooruというサイトを知った。 https://msdd.hatenablog.com/entry/2019/11/21/235937msdd.hatenablog.com

sabebooruは、画像のアーカイブをタグ付きで保存してあるサイトで、検索もできる。

f:id:msdd:20191223023843p:plain
safebooru

画像が大量に欲しいので、このサイトからダウンロードすることにした。 

画像ダウンロード

apiがあったので、それを使った。

api アクセスのurl: https://safebooru.org/index.php?page=dapi&s=post&q=index

limit:どれだけの投稿を検索するか。max100。
pid : ページ数。
tags: 検索するタグ。

などを追加して検索できる。 とりあえず、https://safebooru.org/index.php?page=dapi&s=post&q=indexにアクセスしてみると、xmlが表示されたので、pythonでxmlを扱って、urlから画像ダウンロードするスクリプト作った。

f:id:msdd:20191223055405p:plain
xml

api_strはページ番号pidと検索するtagsを設定した文字列で、これを送りxmlを取得する。 今回は、一人の顔画像が欲しいので、タグはface 1girl soloのものをダウンロードすることにした。

api_str="https://safebooru.org/index.php?page=dapi&s=post&q=index&pid={pid}&tags={tags}".format(pid=page,tags=request_tags.replace(" ","+"))

with urllib.request.urlopen(api_str) as response:
      content=response.read()

pythonのxmlはElementTreeというもので扱うようだ。xmlをパースした後、それぞれ投稿の画像urlを取得した。一回のリクエストで、100件得られる。

xml=ET.fromstring(content)

for post in xml.iter("post"):
       file_url=post.get("file_url")

これを今回は50回ループさせて、5000枚集めた。サイズは3.3G。集めた画像のサンプルが下の画像。

f:id:msdd:20191225005237p:plain
サンプル

きちんと、一人のキャラの顔が写っている画像を集めることができた。

今後

このデータを使ってGANでアニメ画像生成していきたい。

コード置き場

github.com

参考サイト

safebooru.org qiita.com note.nkmk.me

PyTorchのネットワーク構造を可視化できるものを探してみた

はじめに

PyTorchでネットワーク構造を見たいけど、何使えばいいかわからなかったので、探した。

import torch
import torch.nn as nn
import torch.nn.functional as F

class TestModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1=nn.Linear(10,5)
        self.fc2=nn.Linear(5,2)
        

    def forward(self,x):
        x=F.relu(self.fc1(x))
        x=F.softmax(self.fc2(x))
        return x

model=TestModel()
print(model)

テストで使うネットワーク。FC層2層のテストモデル。

TestModel(
  (fc1): Linear(in_features=10, out_features=5, bias=True)
  (fc2): Linear(in_features=5, out_features=2, bias=True)
)

可視化手法

  1. onnxに変換してnetronで見る
  2. tensorboardで見る
  3. PyTorchVizで見る

1. onnxに変換してnetronで見る

まず、pytorchのモデルをonnx形式に変換する。

dummy_input=torch.randn(1,10)#ダミーの入力を用意する
input_names = [ "input"]
output_names = [ "output" ]

torch.onnx.export(model, dummy_input, "./test_model.onnx", verbose=True,input_names=input_names,output_names=output_names)

実行すると、下のような出力とtest_model.onnxというonnxファイルが出力される。

graph(%input : Float(1, 10),
      %fc1.weight : Float(5, 10),
      %fc1.bias : Float(5),
      %fc2.weight : Float(2, 5),
      %fc2.bias : Float(2)):
  %5 : Float(1, 5) = onnx::Gemm[alpha=1, beta=1, transB=1](%input, %fc1.weight, %fc1.bias), scope: TestModel/Linear[fc1] # /usr/local/lib/python3.6/dist-packages/torch/nn/functional.py:1370:0
  %6 : Float(1, 5) = onnx::Relu(%5), scope: TestModel # /usr/local/lib/python3.6/dist-packages/torch/nn/functional.py:914:0
  %7 : Float(1, 2) = onnx::Gemm[alpha=1, beta=1, transB=1](%6, %fc2.weight, %fc2.bias), scope: TestModel/Linear[fc2] # /usr/local/lib/python3.6/dist-packages/torch/nn/functional.py:1370:0
  %output : Float(1, 2) = onnx::LogSoftmax[axis=1](%7), scope: TestModel # /usr/local/lib/python3.6/dist-packages/torch/nn/functional.py:1317:0
  return (%output)

onnx形式に変換後、Netronというもので可視化できる。こののページにonnxファイルをアップロードするとネットワーク構造が見れる。

f:id:msdd:20191218023623p:plain
netronでonnxファイルを可視化
層の名前は変わってしまうが、きちんと表示されている。

このnetronはonnxに変換せずにもpytorchの保存したファイルでも表示はできるみたい。しかし、サイトには実験的サポートと書いてある。

torch.save(model,"./test_model.pth")

保存されたファイルをアップロードしてみると、ネットワーク構造は表示されたが、relu、log_softmaxが表示されなかった。

f:id:msdd:20191218023811p:plain
netronでpytorch形式のファイルを可視化

2. tensorboard

tensorboardのグラフ表示する機能を使う方法。

msdd.hatenablog.com

google colabのtensorflowのバージョンを2系へ変更して、 tensorboardの拡張を読み込む。

%tensorflow_version 2.x
%load_ext tensorboard

add_graphを用いて、モデルの構造の出力する。

from torch.utils.tensorboard import SummaryWriter

dummy_input=torch.randn(1,10)

writer = SummaryWriter()
writer.add_graph(model, dummy_input)
writer.close()

tensorboardを起動して、グラフを見る。

%tensorboard --logdir ./runs

f:id:msdd:20191222025725p:plain
tensorboardグラフ

ダブルクリックすると、中身も見れる。

f:id:msdd:20191222025901p:plain
tensorboardグラフ詳細

3. pytorchvizで見る

pytorchvizを使い、モデルを可視化する。

ライブラリをインストールする。

!apt-get install graphviz
!pip install torchviz

次に、モデルを生成し、入力xを通して出力yを得る。 make_dotの引数に出力とモデルのパラメータを入れると、グラフが出てくる。

from torchviz import make_dot

model=TestModel()
x=torch.randn(1,10)#ダミー入力
y=model(x)

make_dot(y,params=dict(model.named_parameters()))

f:id:msdd:20200114141431p:plain
pytorchvizでモデル構造を可視化したもの

参考サイト

Google Colab上でTensorBoardを使う

はじめに

google colab上でtensorboardを使えるみたいだったので、試してみた。

環境

  • 使用ブラウザ: Firefox 71.0
  • torch 1.3.1
  • torchvision 0.4.2

tensorboard関連のライブラリわからないけど、一応。

  • tensorflow 2.1.0rc1
  • tensorboard 2.1.0
  • tensorboardcolab 0.0.22

実行

ログファイルの準備

今回は、pytorchのtorch.utils.tensorboardを使ってログファイル作った。

tensorflowのバージョンを2系に切り変えておく。

%tensorflow_version 2.x

ログファイルの作成。グラフとスカラー値をログを作った。 グラフは、mobilenet v2のモデルを記録した。

import torch
from torch.utils.tensorboard import SummaryWriter
import torchvision.models as models

model=models.mobilenet_v2()
image=torch.randn(1,3,224,224)
writer = SummaryWriter()
writer.add_graph(model, image)
writer.add_scalar("test",1,1)
writer.add_scalar("test",2,2)
writer.close()

実行すると、runsというフォルダーが出来て、ログが入っている。 これをtensorboard上に表示させてみる。

tensorboardを起動する準備

tensorboard notebookの拡張を読み込む

%load_ext tensorboard

tensorboard起動

%tensorboardでtensorboarを起動する。--logdir の後に生成したログフォルダの場所を指定する。

%tensorboard --logdir ./runs

SCALARSにきちんと追加した値がグラフとして表示されている。

f:id:msdd:20191219170409p:plain
tensorboard起動時のSCALARS画面

GRAPHSの方もmobilenet v2の構造が表示されている。

f:id:msdd:20191219170622p:plain
tensorboard GRAPHS画面

気になる点、今後

firefoxのプライベートウィンドウで実行すると、下のように403が表示された。

f:id:msdd:20191219171802p:plain

chromeのシークレットウィンドウでも同じことになるのかと思って試してもならなかった。 原因はわからなかった。

学習結果をすぐに見れるのは便利なので、今後学習の時に使っていきたい。

参考サイト

Google Colabで使われているgpuの種類の調査

はじめに

Google ColaboratoryにはGPUガチャがあるよという話 - Qiita
この記事読んで、google colabのgpuでも色々な種類のものがあることを知った。

画像や動画でdeep learningをする時、gpuの性能が重要なことが多い。 deep learningの学習は、多くの時間がかかり、 google colabは、時間制限がある。 そのため学習時は、出来るだけ性能のいいgpuを使いたい。 そこで、どのようなgpuがあるのかを調べてみた。

調べ方は簡単で、コマンド!nvidia-smi名前長くて見れないやつは!nvidia-smi -Lで gpuの名前見て、その名前を検索して調べた。 このコマンドは、gpuの名前や使用率を見ることができるコマンドで、よくgpu使用率を見る時に 使用している。

f:id:msdd:20200503133137j:plain

gpuの調査

gpuの種類

実際にgoogle colabを使っていて、割り当てられたものは下のようになった。

自分でgoogle colabを使用していたが、割り当てられたのを見たことがない、未確認のものは下のようになった。

  • Tesla T4

全てNvidia Teslaシリーズであった。

短くにまとめてみる。 注意点としては、Tesla K80は2つのgpuのうち1つのみ使えるらしく、Nvidiaの実際のスペック表に書いてあるものの半分だけ使えると思われる。 下の表には半分にした値を載せた。

GPU名前 発売日  メモリ容量 CUDAコアプロセッサ数 単精度浮動小数点性能  
Tesla K80 2014年11月 12GB 2496 コア 4.37 TFLOPS (GPU Boost Clocks), 2.8 TFLOPS (Base Clocks)
Tesla P100-PCIE-16GB 2016年6月 16GB 3584 コア 9.3 TFLOPS(最大ブースト)
Tesla P4 2016年9月 8GB 2560 コア 5.5 TFLOPS(最大ブースト)

wikipediaを参考にした。

この表を見てみると、メモリ容量に関しては、P100がP4の2倍の16GBもあり、かなり容量が多い。 deep learningでは、メモリを多く使うのでこれはうれしい。コア数でも、やはりP100が多い。 なので、計算速度もP100が速いと思われる。K80とP4は、どちらが速いのかはわからない。

GeForceシリーズと比較

Teslaシリーズは、普段は見ることがないので、よく見る方のgeforceシリーズと比べてみる。 TeslaシリーズのP100と比較した。 GeForceシリーズは、最近のものだとRTX 2080 super,RTX 2070 superなどや、前に売られていたGTX 1080などのシリーズ。

GPU名前 発売日  メモリ容量 CUDAコアプロセッサ数 単精度浮動小数点性能  
Tesla P100-PCIE-16GB 2016年6月 16GB 3584 コア 9.3 TFLOPS(最大ブースト)
RTX 2080 Ti 2018年8月 11GB 4352 コア 12.4 TFLOPS
RTX 2070 SUPER 2019年7月 8GB 2560 コア 8.2 TFLOPS

wikipediaを参考にした。

P100と比べると、GeForce RTX 2080 Tiはメモリが5GBほど少ないが、P100より新しいのでコア数は多く演算は早いと思われる。

nvidia-smiで取得したSS

f:id:msdd:20191217213323p:plain
Tesla K80

f:id:msdd:20191214194622p:plain
Tesla P4

f:id:msdd:20191215022726p:plain
Tesla P100-PCIE-16GB

おわりに

google colabで使われているgpuの種類をnvidia-smiのコマンドを使って、名前を調べると、 NVIDIA Teslaシリーズのgpuが使われていることがわかった。 さらに、割り当てられたことのある3つのgpuについて、性能やメモリ容量などをまとめてみた。

メモリでは、P100>K80>P4の順で容量が大きく、P100がP4の2倍の16Gとかなり大きい。 計算速度もP100が速い。K80とP4は、どちらが速いのかはわからない。

無料で使わせてもらえるのはとてもありがたい。 ありがたく使わせてもらいますm( )m。

新しいの見つけたら更新していきます。