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

複数の画像を敷き詰めてプロットする方法をいつも忘れちゃう(ML向け)

はじめに

画像データをこういう感じにプロットするやり方をいつも忘れてしまう自分用メモです。
subplot.png

前提

データセットはこのようなディレクトリ構成になっていることを想定しています。
├─Dataset
│ ├─classA
│ │ ├─classAの画像たち
│ ├─classB
│ ├─classC
│ ├─classD
│ ├─classF
│ └─...
...

ソースコード

show_class_images.py
import glob
import os

import matplotlib.pyplot as plt
from PIL import Image

# データセットの親ディレクトリ
DATA_PARENT_DIR = '親ディレクトリを指定'
# 子ディレクトリのリスト
data_dir_list = glob.glob(DATA_PARENT_DIR + '\*')
# ディレクトリ数
class_num = len(data_dir_list)
# 1ディレクトリあたりに敷き詰める画像数
SHOW_FILE_NUM = 6

fig = plt.figure(figsize=(12, 12))

# subplot用のカウント
cnt = 1

for data_dir in data_dir_list:
    filepath_list = glob.glob(data_dir + '\*')
    dirname = os.path.basename(data_dir)

    # ディレクトリ内のファイル
    for i in range(SHOW_FILE_NUM):
        filepath = filepath_list[i]
        filename = os.path.basename(filepath)
        img = Image.open(filepath)
        # 6*6の1マス分を指定(cnt)してプロット
        ax = fig.add_subplot(class_num, SHOW_FILE_NUM, cnt)
        plt.imshow(img)
        # 目盛りを消す
        ax.axis('off')
        # ディレクトリ名(クラス名)+ファイル名をタイトルに出す
        plt.title(os.path.join(dirname, filename))
        # 描画用カウントアップ
        cnt += 1

plt.show()

# 保存する
fig.savefig('./subplot.png')

引用

データセットはこちらからお借りしました。ありがとうございました。
DiceRecognitionDatasetForML

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

MacでPythonの環境作りからTableauでTabPyを利用するまで

はじめに

最近、プログラミングに触れることも少なくなったのだけど、TableauでTabPyを利用する必要がでたので、Pythonを書く訳ではないもののPythonの環境作りをして、TabPyを利用できる準備をしてみた。
TabPyとは、BIツールであるTableauからPythonの機械学習モデルを呼び出せる仕組み。

随分、プログラミング環境も整える機会も減ってしまったので、まずは brew をインストールして必要なパッケージを揃えてきいく。

目次

1.Pythonの環境準備
2.TabPyのインストール
3.おまけ(TableauでのTabPy活用

1.Pythonの環境準備

Pythonの環境は以下で実現

# pyenvの準備
$ brew install pyenv
$ echo 'eval "$(pyenv init -)"' >> ~/.zshrc    
$ exec $SHELL -l

# インストールできるVersionを確認
$ pyenv install --list

# インストールできるVersionを確認
$ pyenv install --list

# pythonのインストール
$ pyenv install 3.7.9
$ pyenv global 3.7.9 
$ eval "$(pyenv init -)"
$ python --version
Python 3.7.2

# anacondaのインストール
$ pyenv install anaconda3-2020.11
$ pyenv global anaconda3-2020.11

2.TabPyのインストール

TabPyのインストールは、Githubを参考に。
https://github.com/tableau/TabPy
以下に手順はあったので容易にインストールはできる。
https://github.com/tableau/TabPy/blob/master/docs/server-install.md

#pipを最新に
$ python -m pip install --upgrade pip

#TabPyをインストール
$ pip install tabpy

#TabPyの起動確認
$ tabpy

#以下のような起動メッセージがでれば起動完了(portなどは後のTableauで利用する
[INFO] (app.py:app:105): Web service listening on port 9004

3.おまけ(TableauでのTabPy活用

最終的に実施したかったのは、TableauでのTabPyを利用したかったので、上記で起動したTabPyに接続をする。

Tableau DesktopからTabpy Serverに接続

  1. Tableau Desktopを起動
  2. ヘルプ → 設定とパフォーマンス → 分析の拡張機能の接続 を選択
  3. TabPy/External API を選択して、サーバを localhost、ポートが 9004であることを確認して、テスト接続を実施
  4. OK を押して接続が完了

tableau_tabpy.png

Pythonの環境作りやTabPyの起動までは意外とさまよった部分はあったのだが、結果として整理してみると意外とさらっと終わる手順であった。

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

【Python】 2つのListを並行処理する方法

概要

  • 2つのListを並行処理する方法  

1: for文を使用する方法
2: zip関数を使用する方法

※ 2 の方が可読性が上がるので、2 を使用することを推奨。

検証環境

OS:18.04.5 LTS
Python:3.6.9

方法

2つのlistをで並行処理

1: for文を使用する方法

基本形

for 変数1, 変数2 in enumerate( list_1 ):
    print('{0}, {1}').format(変数2, list_2[変数1]) 

実行結果

list_1 = [1,2,3,4,5,6,7,8,9,10];
list_2 = ['1','2','3','4','5','6','7','8','9','10'];

# 並行処理
for i, j in enumerate( list_1 ):
    print('{0}, {1}'.format( j, list_2[i] ));

# 結果
1, 1
2, 2
3, 3
4, 4
5, 5
6, 6
7, 7
8, 8
9, 9
10, 10

2: zip関数を使用する方法

基本形

for 変数1, 変数2 in zip(list_1, list_2):
    print('{0}, {1}'.format( 変数1, 変数2 ))

実行結果

list_1 = [1,2,3,4,5,6,7,8,9,10]
list_2 = ['1','2','3','4','5','6','7','8','9','10']

# 並行処理
for i, j in zip(list_1, list_2):
    print('{0}, {1}'.format( i, j ))

# 結果
1, 1
2, 2
3, 3
4, 4
5, 5
6, 6
7, 7
8, 8
9, 9
10, 10

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

PythonのPytestでTDDするときimportがエラーになる

あらすじ

Pytestを使っていて気が付いたことです。
これは入門者泣かせですね。

別なのかよ

F5デバッグや、Debug testでは正常にインポートされますが、Run testすると下記のエラーが出ました。

E ModuleNotFoundError: No module named `??????????`

Pythonでは、importするときの Base Directory として、sys.path内にリストされたアドレスを使います。

PylintやF5デバッグ、Debug testでは、settings.jsonpython.envFile内に記述した、PYTHONPATH=./が Base Directory です。

ところがRun testでは、Base Directory を決める際に、実行するテストのあるディレクトリから上に向かって検索し、__init__.pyが無い最初のディレクトリを Base Directory として登録するようです。

Note: Good Integration Practices

環境

.vscode/extensions.json
{
    "recommendations": [
        "ms-python.python",
        "visualstudioexptteam.vscodeintellicode",
        "oderwat.indent-rainbow",
        "kameshkotwani.google-search"
    ]
}
.vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Current File",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "justMyCode": true
        }
    ]
}
.vscode/settings.json
{
    "python.testing.pytestEnabled": true,
    "python.formatting.provider": "black",
    "python.envFile": "${workspaceFolder}/.env",
}

Excelsior!

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

PCCSをRGBの3D散布図で可視化してみた

はじめに

色彩検定の勉強をし始めると、はじめにPCCSという色の表記方法に出会います。
自分はRGBの表記しか知らなかったので、

「PCCSとRGBってどんな関係なの?」
「同じ色相のままトーンを変えるとRGBはどう変化するの?」

といった疑問が沸きました。
そこで、RGBの3D散布図にすれば関係がわかるのでは?と思い立ち、実際にやってみました。

PCCSとRGBの対応関係

こちらの記事から得ました。

PCCSの3D散布図

Pythonでmatplotlibを用いて可視化しました。

  1. BeautifulSoupでスクレイピングし対応関係を表に
  2. 表から欲しいデータを取り出し、matplotlibとAxes3Dで3D散布図に
  3. BytoIOとImageを用いてgifとして保存

360度回転もできるのですが、5GB以下にしたかったので20度~70度で回転させてます。

同じトーンで表示

Vivid

v0_PCCS_3d.gif
まずはVividの色相環。
色相環というだけあって、三次元で表しても順番に一周しているのが見て取れます。
個人的にはもっときれいな円形になってると思ってました。

Nuetral

Gy0_PCCS_3d.gif
無彩色。
R, G, Bがすべて同じ値を取るのでこういった直線に並びます。
つまり、この直線に近くなるほど彩度が低くなるということです。
また、画面手前に来るほど明度が高くなるということもうかがえます。

Pale

p+0_PCCS_3d.gif
Vivid、Nuetralが分かればあとはなんとなく想像つくと思います。
Paleは明度が高いのでかなり画面手前で円を描きます。
逆に彩度は落ちるので一つ前のNuetralの直線に集まります。

dark

dk0_PCCS_3d.gif
最後に一つだけ。
Darkは明度が低いので画面奥になり、かつ彩度も低い方なのでNuetralの直線に近い位置で円を描きます。

同じ色相で表示

心理4原色でトーンを変えて表示。

2 : R

All2_PCCS_3d.gif

8 : Y

All8_PCCS_3d.gif

12 : G

All12_PCCS_3d.gif

18 : B

All18_PCCS_3d.gif

すべてトーン図通りのきれいな同一平面のグラフが得られました。

分かったこと

  • Vividの表から色相ごとのRGBのイメージがつかめる。
  • 同じ色相ならトーンは同じ平面上にある。
  • 彩度を落とすとR=G=Bに近づく。その逆もしかり。
  • 明度を上げるとR, G, Bは255に近づく。その逆もしかり。

3D散布図により、PCCSや色の三属性について、RGBにおけるざっくりとしたイメージがつかめました。

最後に

  1. 全部表示させてみる。
    All0_PCCS_3d.gif
    きれいになると思ったのに暗めの色が邪魔してる。。。

  2. 軸の色を変えられればもっと見やすくなるかも、と思ったが、Axes3Dの扱い方がむずかしく、わからなかったので一旦保留。

  3. Munsell ⇒ RGBの変換もやる価値がありそう。

  4. コードはいつか上げるかも。

参考

PCCS相互変換表 - garakuta.net
3D 散布図を回転 GIF アニメーションにする

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

PCCSをRGBの3D散布図で可視化

はじめに

色彩検定の勉強をし始めると、はじめにPCCSという色の表記方法に出会います。
自分はRGBの表記しか知らなかったので、
「PCCSとRGBってどんな関係なの?」
「同じ色相のままトーンを変えるとRGBはどう変化するの?」
といった疑問が沸きました。
そこで、RGBの3D散布図にすれば関係がわかるのでは?と思い立ち、実際にやってみました。
  

PCCSとRGBの対応関係

こちらの記事から得ました。
  

PCCSの3D散布図

Pythonでmatplotlibを用いて可視化しました。

  1. BeautifulSoupでスクレイピングし対応関係を表に
  2. 表から欲しいデータを取り出し、matplotlibとAxes3Dで3D散布図に
  3. BytoIOとImageを用いてgifとして保存

360度回転もできるのですが、5GB以下にしたかったので20度~70度で回転させてます。
  

同じトーンで表示

Vivid

v0_PCCS_3d.gif
まずはVividの色相環。
色相環というだけあって、三次元で表しても順番に一周しているのが見て取れます。
個人的にはもっときれいな円形になってると思ってました。

Nuetral

Gy0_PCCS_3d.gif
無彩色。
R, G, Bがすべて同じ値を取るのでこういった直線に並びます。
つまり、この直線に近くなるほど彩度が低くなるということです。
また、画面手前に来るほど明度が高くなるということもうかがえます。

Pale

p+0_PCCS_3d.gif
Vivid、Nuetralが分かればあとはなんとなく想像つくと思います。
Paleは明度が高いのでかなり画面手前で円を描きます。
逆に彩度は落ちるので一つ前のNuetralの直線に集まります。

dark

dk0_PCCS_3d.gif
最後に一つだけ。
Darkは明度が低いので画面奥になり、かつ彩度も低い方なのでNuetralの直線に近い位置で円を描きます。
 

同じ色相で表示

心理4原色でトーンを変えて表示。

2 : R

All2_PCCS_3d.gif

8 : Y

All8_PCCS_3d.gif

12 : G

All12_PCCS_3d.gif

18 : B

All18_PCCS_3d.gif

すべてトーン図通りのきれいな同一平面のグラフが得られました。
  

分かったこと

  • Vividの表から色相ごとのRGBのイメージがつかめる。
  • 同じ色相ならトーンは同じ平面上にある。
  • 彩度を落とすとR=G=Bに近づく。その逆もしかり。
  • 明度を上げるとR, G, Bは255に近づく。その逆もしかり。

3D散布図により、PCCSや色の三属性について、RGBにおけるざっくりとしたイメージがつかめました。
 

最後に

  1. 全部表示させてみる。
    All0_PCCS_3d.gif
    きれいになると思ったのに暗めの色が邪魔してる。。。

  2. 軸の色を変えられればもっと見やすくなるかも、と思ったが、Axes3Dの扱い方がむずかしく、わからなかったので一旦保留。

  3. Munsell ⇒ RGBの変換もやる価値がありそう。

  4. コードはいつか上げるかも。

参考

PCCS相互変換表 - garakuta.net
3D 散布図を回転 GIF アニメーションにする

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

PCCSを3D散布図で可視化

はじめに

色彩検定の勉強をし始めると、はじめにPCCSという色の表記方法に出会います。
自分はRGBの表記しか知らなかったので、
「PCCSとRGBってどんな関係なの?」
「同じ色相のままトーンを変えるとRGBはどう変化するの?」
といった疑問が沸きました。
そこで、RGBの3D散布図にすれば関係がわかるのでは?と思い立ち、実際にやってみました。
  

PCCSとRGBの対応関係

こちらのサイトから得ました。
  

PCCSの3D散布図

Pythonでmatplotlibを用いて可視化しました。

  1. BeautifulSoupでスクレイピングし対応関係を表に
  2. 表から欲しいデータを取り出し、matplotlibとAxes3Dで3D散布図に
  3. BytoIOとImageを用いてgifとして保存

360度回転もできるのですが、5GB以下にしたかったので20度~70度で回転させてます。
  

同じトーンで表示

Vivid

v0_PCCS_3d.gif
まずはVividの色相環。
色相環というだけあって、三次元で表しても順番に一周しているのが見て取れます。
個人的にはもっときれいな円形になってると思ってました。

Nuetral

Gy0_PCCS_3d.gif
無彩色。
R, G, Bがすべて同じ値を取るのでこういった直線に並びます。
つまり、この直線に近くなるほど彩度が低くなるということです。
また、画面手前に来るほど明度が高くなるということもうかがえます。

Pale

p+0_PCCS_3d.gif
Vivid、Nuetralが分かればあとはなんとなく想像つくと思います。
Paleは明度が高いのでかなり画面手前で円を描きます。
逆に彩度は落ちるので先ほどのNuetralの直線に集まります。

dark

dk0_PCCS_3d.gif
最後に一つだけ。
Darkは明度が低いので画面奥になり、かつ彩度も低い方なのでNuetralの直線に近い位置で円を描きます。
 

同じ色相で表示

心理4原色でトーンを変えて表示。

2 : R

All2_PCCS_3d.gif

8 : Y

All8_PCCS_3d.gif

12 : G

All12_PCCS_3d.gif

18 : B

All18_PCCS_3d.gif

すべてトーン図通りのきれいな同一平面のグラフが得られました。
  

分かったこと

  • Vividの表から色相ごとのRGBのイメージがつかめる。
  • 同じ色相ならトーンは同じ平面上にある。
  • 彩度を落とすとR=G=Bに近づく。その逆もしかり。
  • 明度を上げるとR, G, Bは255に近づく。その逆もしかり。

3D散布図により、PCCSや色の三属性について、RGBにおけるざっくりとしたイメージがつかめました。
 

最後に

  1. 全部表示させてみる。
    All0_PCCS_3d.gif
    きれいになると思ったのに暗めの色が邪魔してる。。。

  2. 軸の色を変えられればもっと見やすくなるかも、と思ったが、Axes3Dの扱い方がむずかしく、わからなかったので一旦保留。

  3. Munsell ⇒ RGBの変換もやる価値がありそう。

  4. コードはいつか上げるかも。

参考

PCCS相互変換表 - garakuta.net
3D 散布図を回転 GIF アニメーションにする

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

B - Bingo, C - Takahashi's Information, C - 山崩し

B - Bingo

O(N)
実装問題です。

python
import math
import heapq
import itertools
from functools import reduce

# main
def main():
    A = []
    Field = [[False] * 3, [False] * 3, [False] * 3]

    for i in range(0, 3):
        a = list(map(int, input().split()))
        A.append(a)

    N = int(input())
    B = [0] * N
    for i in range(0, N):
        B[i] = int(input())

    for b in B:
        for i in range(3):
            for j in range(3):
                if A[i][j] == b:
                    Field[i][j] = True

    bingo = False
    if Field[0][0] and Field[1][1] and Field[2][2]:
            bingo = True

    if Field[0][2] and Field[1][1] and Field[2][0]:
            bingo = True

    for i in range(3):
        if Field[i][0] and Field[i][1] and Field[i][2]:
            bingo = True

    for i in range(3):
        if Field[0][i] and Field[1][i] and Field[2][i]:
            bingo = True

    if bingo:
        print("Yes")
    else:
        print("No")

# エントリポイント
if __name__ == '__main__':
    main()

C - Takahashi's Information

O(1)
問題文から下記の要素が推測できる。
引き算をして各要素が等しいか判定。

a_1 + b_1,  a_1 + b_2, a_1 + b_3 \\
a_2 + b_1,  a_2 + b_2, a_2 + b_3 \\
a_3 + b_1,  a_3 + b_2, a_3 + b_3 \\

python
import math
import heapq
import itertools
from functools import reduce

# main
def main():
    C = []

    for _ in range(0, 3):
        row = list(map(int, input().split()))
        C.append(row)

    ok = True
    if C[0][0] - C[0][1] != C[1][0] - C[1][1] or C[1][0] - C[1][1] != C[2][0] - C[2][1]:
        ok = False
    if C[0][1] - C[0][2] != C[1][1] - C[1][2] or C[1][1] - C[1][2] != C[2][1] - C[2][2]:
        ok = False

    if ok:
        print("Yes")
    else:
        print("No")

# エントリポイント
if __name__ == '__main__':
    main()

C - 山崩し

O(N*(2N-1))
全探索の問題です。
実装問題。

5
....#....
...##X...
..#####..
.#X#####.
#########

配列の下から判定すると上手くいきますね。

pyhton
import math
import heapq
import itertools
from functools import reduce

# main
def main():
    N = int(input())
    field = []

    for _ in range(0, N):
        S = str(input())
        field.append(S)

    for i in range(N-1, 0, -1):
        for j in range(0, 2*N-1):
            if field[i][j] == 'X':
                if j-1>=0 and field[i-1][j-1] == '#':
                    field[i-1] = field[i-1][:j-1] + 'X' + field[i-1][j:]

                if field[i-1][j] == '#':
                    field[i-1] = field[i-1][:j] + 'X' + field[i-1][j+1:]

                if j+1<2*N-1 and field[i-1][j+1] == '#':
                    field[i-1] = field[i-1][:j+1] + 'X' + field[i-1][j+2:]

    for i in range(0, N):
        print(field[i])

# エントリポイント
if __name__ == '__main__':
    main()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】Listを文字列として出力する方法

概要

1:文字列を含むlistを文字列に変換
2:数値を含むlistを文字列に変換

検証環境

OS:18.04.5 LTS
Python:3.6.9

方法

文字列を含むlistを文字列に変換

list = ['v','e','r','i','f','y']
print(''.join(list))

# 実行結果
verify

数値を含むリストを文字列に変換

list = [0,1,2,3,4,5]
print(''.join(map(str,list)))

# 実行結果
012345
data = [0,1,2,3,4,5]
print(*data, sep='')

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

Pandasの使い方3

1 この記事は

DataFrame型データの各種操作方法をメモする。

2 内容

2-1 DataFrame型の各列に関数を作用させてデータを生成する。

DataFrame型dfの列C > 8 かつ 列D<90のとき、D1列に1を書き込む作業を行う。

sample.py
import pandas as pd
import numpy as np
import scipy.stats


#関数定義  DataFrame型xの列C > 8 かつ 列D<90のとき、D1列に1を書き込む
def judge(x):
    x.loc[ ( (x['C'] > 8 ) & (x['D']<90) ) , 'D1'] = 1
    return x['D1']



#dataを定義する。
dat = [
    [100,'2019-07-01',2,7,0,0],
    [100,'2019-07-02',4,77,0,0],
    [100,'2019-07-03',8,8,0,0],
    [100,'2019-07-04',16,89,0,0],    
    [200,'2020-07-06',100,9,0,0],
    [200,'2019-07-05',200,99,0,0],
    [200,'2019-07-04',400,123,0,0],
    [200,'2019-07-03',200,345,0,0],    
]

#datをDataFrame型変数dfに格納する。
df = pd.DataFrame(dat,columns=["A","B","C","D","D1","D2"])

#A,B列をindexに指定する。
df=df.set_index(["A","B"])

#DataFrame dfをindex Aでgroupbyさせる。df.groupby("A")に関数judgeを作用させる。judgeはseries型で返却されるので、to_frameでDataFrame型に変換し、dfのD1列に書き込む。
df['D1']= df.groupby("A").apply(lambda x:judge(x).to_frame('any'))

print("df",df)

実行結果

df                   C    D  D1  D2
A   B                           
100 2019-07-01    2    7   0   0
    2019-07-02    4   77   0   0
    2019-07-03    8    8   0   0
    2019-07-04   16   89   1   0
200 2020-07-06  100    9   1   0
    2019-07-05  200   99   0   0
    2019-07-04  400  123   0   0
    2019-07-03  200  345   0   0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ROSの勉強 第20弾:環境の地図作成

#プログラミング ROS< 環境の地図作成 >

はじめに

1つの参考書に沿って,ROS(Robot Operating System)を難なく扱えるようになることが目的である.その第20弾として,「環境の地図作成」を扱う.

環境の地図作成

まず,地図を作成する対象環境を以下に示す.
testmap.PNG

ここでは,turtlebotを動かし,センサデータから地図作成を行う.前回(https://qiita.com/Yuya-Shimizu/items/f69b18e8dd2cee392e79 )で学んだrosbagが大いに活躍する.

手順

以下では,動画像を使って手順を示す.

手順1

対象環境にセンサを搭載したロボットを配置(gazeboの起動)
process1.gif

手順2

ロボットを動かす準備と,データを記録する準備(teleopとrosbag)

process2.gif
実行したコマンドは以下のとおりである.

~$ rosbag record -O data.bag /scan /tf
~$ roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch

これで,data.bagに/scanと/tfのデータを記録する準備ができた.

手順3

データ収集(ロボットを動かす)
process3.gif
しばらく適当にデータを収集し続ける.

データ収集後,rosbag record~を実行しているターミナル(端末)上でCtrl-cを押して,データの記録終了.そのとき,data.bagというファイルができている.このファイルの情報を詳しく見ることができる.

キャプチャ.PNG

こんな感じである.

手順4

データから地図を生成する(gmappingとrosbag)
process4.gif

ターミナル1
~$ rosrun gmapping slam_gmapping
ターミナル2
~$ rosbag play --clock data.bag
ターミナル3
~$ rosrun robot_state_publisher robot_state_publisher

はじめは,ターミナル1と2だけ実行していたが,これではうまく地図を生成できていなかった.どうやら,rvizのとき同様,robotからmapにデータがうまく変換がされていないようだ.そこで,rvizのときの解決策と同じようにターミナル3を実行してから行ってみたところ,うまくいった.必ず,シミュレータを停止して,ターミナル3を実行してから,ターミナル1,ターミナル2という順で実行する.
これで,地図の作成ができた.

手順5

地図データの保存(map_server)
process5.gif

~$ rosrun map_server map_saver

このとき,手順4でのターミナル2は起動したままとする.なぜなら,そのデータを今から保存しようとしているため.保存が完了すると,地図の画像データと,メタデータを含むyamlファイルが得られる.以下にそれらを示す.
map.PNG

map.yaml
image: map.pgm
resolution: 0.050000
origin: [-100.000000, -100.000000, 0.000000]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.196

地図の質を向上させる

先ほど得られた地図の画像については,デフォルトではサイズが4000*4000であったため,非常に小さく表示されてしまっていた.また,地図自体もあまりよくないらしい.そこで,ある設定をこなすだけでよくなる方法をここで示しておく.

rosparam set /slam_gmapping/angularUpdate 0.1
rosparam set /slam_gmapping/linearUpdate 0.1
rosparam set /slam_gmapping/lskip 10
rosparam set /slam_gmapping/xmax 10
rosparam set /slam_gmapping/xmin -10
rosparam set /slam_gmapping/ymax 10
rosparam set /slam_gmapping/ymin -10

これらを,gmapping実行前に設定する.rosparam setにより,gmappingのパラメータを変更している.上記のものは,新しいスキャンデータを地図に取り込むまでにロボットが動かないといけない回転量(angularUpdate)と並進移動の量(linearUpdate),LaserScanメッセージを処理するときに飛ばして読む光線の数(lskip),地図の広さ(xmin, xmax, ymin, ymax)を変更している.以下にそのときの結果を示す.
map2.PNG

map2.yaml
image: map2.pgm
resolution: 0.050000
origin: [-10.000000, -10.000000, 0.000000]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.196

上の地図を見ても分かるように,地図の広さの指定により程よい大きさの地図画像が生成された.非常に見やすくなった.このあたりの設定は,ここでは示さないが,launchファイルなどにまとめておくと便利かもしれない.

感想

rvizのときもそうだったが,今回も気づきにくい(明示的にエラーとは示してくれない)ものが,原因となってうまく地図の作成ができず,1日2日悩んだ.何とか解決できてよかった.何か別の空間や形式に持っていくときには,何かしらの変換が必要で,そのための変換器のようなものが必要になることもあるということを頭に入れて,意識しておいた方がいいと身をもって感じた.なお,上では示さなかったが,リアルタイム(bagファイルに保存しない)で地図を作成することも可能で,その場合,ロボットを動かしているときに,slam_gmappingを起動するだけである.しかしながら,ロボットの計算負荷を減らすために,データをいったん記録することがお勧めらしい.ただし,rosbagで保存するかどうかにかかわらず,結果的には同じような地図ができるらしい.

今回扱ったgmappingについての詳細は,論文が2つある.ここにそのURLを示しておく.
論文1: Improved Techniques for Grid Mapping With Rao-Blackwellized Particle Filters
(https://www.researchgate.net/publication/3450390_Improved_Techniques_for_Grid_Mapping_With_Rao-Blackwellized_Particle_Filters )
論文2: Improving Grid-based SLAM with Rao-Blackwellized Particle Filters By Adaptive Proposals and Selective Resampling
(https://www.researchgate.net/publication/221067958_Improving_Grid-based_SLAM_with_Rao-Blackwellized_Particle_Filters_By_Adaptive_Proposals_and_Selective_Resampling )

また,余談だがQiitaの記事を作成する際に,どうやら10MBを超える画像は貼り付けられないらしい.そういう公式の発表を見たわけではないが,10MBを超えたgifデータだけ貼り付けられず,圧縮して軽量化することで,貼り付けられた.

参考文献

プログラミングROS Pythonによるロボットアプリケーション開発
        Morgan Quigley, Brian Gerkey, William D.Smart 著
                       河田 卓志 監訳
            松田 晃一,福地 正樹,由谷 哲夫 訳
                  オイラリー・ジャパン 発行

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

[Python]辞書を1行ずつファイルに書き込む

keyとvalueの組を1行ずつファイル出力するプログラム
パッケージのimportなどは不要

dic = dict(key1="val1", key2="val2", key3="val3")

#ファイル出力
with open("output.csv", mode='w') as f:
    f.writelines("\n".join(str(k)+","+str(v) for k,v in dic.items()))
# key1,val1
# key2,val2
# key3,val3

注意点
joinの中に二重リストや二重タプルを入れることはできません.
また異なる型をjoinで結合しようとするとエラーを吐くため,結合前に各変数をstr型に変換してあげないといけません.

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

Git Bashで対話モードのPythonのコマンド履歴と補完を有効化する

対処法

以下のコマンドでpyreadlineをインストールします。

$ pip install pyreadline

これで上下矢印キーでの履歴参照とTab補完が効くようになります。
Anaconda環境の場合はpipをcondaに読み替えてください。

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

WaniCTF complex writeup ( Ghidra を使用した静的解析 )

Ghidraを勉強した記録として残しておく。
stack strings の問題だけど byte だけではなく,qword, dword, word も登場する。

complexの入手先

表層解析で得られる文字列 Incorrect を検索
main関数だ

undefined8 main(void)
{
  int iVar1;
  size_t sVar2;
  char local_78 [48];
  char local_48 [5];
  char acStack67 [37];
  char acStack30 [14];
  int local_10;
  int local_c;

  printf("input flag : ");
  __isoc99_scanf(&DAT_00101dc2,local_48);
  sVar2 = strlen(local_48);
  if (((sVar2 != 0x2b) || (iVar1 = strncmp(local_48,"FLAG{",5), iVar1 != 0)) ||
     (iVar1 = strcmp(acStack30,"}"), iVar1 != 0)) {
    puts("Incorrect");
    return 1;
  }
  strncpy(local_78,acStack67,0x25);
  local_c = 0;
  do {
    if (0x13 < local_c) {
      return 0;
    }
    local_10 = check(local_c,local_78,local_78);
    if (local_10 != 0) {
      if (local_10 == 1) {
        puts("Incorrect");
        return 1;
      }
      if (local_10 == 2) {
        printf("Correct! Flag is %s\n",local_48);
        return 0;
      }
    }
    local_c = local_c + 1;
  } while( true );
}

check関数のリターンが 2 だと Correct! になるようだ。

check関数

void check(undefined4 param_1,undefined8 param_2)
{
  switch(param_1) {
  case 0:
    check_0(param_2);
    break;
  case 1:
    check_1(param_2);
    break;
  case 2:
    check_2(param_2);
    break;
  case 3:
    check_3(param_2);
    break;
  case 4:
    check_4(param_2);
    break;
  case 5:
    check_5(param_2);
    break;
  case 6:
    check_6(param_2);
    break;
  case 7:
    check_7(param_2);
    break;
  case 8:
    check_8(param_2);
    break;
  case 9:
    check_9(param_2);
    break;
  case 10:
    check_10(param_2);
    break;
  case 0xb:
    check_11(param_2);
    break;
  case 0xc:
    check_12(param_2);
    break;
  case 0xd:
    check_13(param_2);
    break;
  case 0xe:
    check_14(param_2);
    break;
  case 0xf:
    check_15(param_2);
    break;
  case 0x10:
    check_16(param_2);
    break;
  case 0x11:
    check_17(param_2);
    break;
  case 0x12:
    check_18(param_2);
    break;
  case 0x13:
    check_19(param_2);
  }
  return;
}

心を折りにきてるな。負けるもんか。(正直,心折れそう)
一つずつ落ち着いてみていく。
check_13関数だけ 2 を返してる

undefined8 check_13(long param_1)

{
  undefined8 local_68;
  undefined8 local_60;
  undefined8 local_58;
  undefined8 local_50;
  undefined4 local_48;
  undefined local_44;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  undefined8 local_20;
  undefined4 local_18;
  undefined local_14;
  int local_c;

  local_38 = 0x3131393431333637;
  local_30 = 0x3435313837393235;
  local_28 = 0x3635313836343636;
  local_20 = 0x3834303131353334;
  local_18 = 0x34323435;
  local_14 = 0x37;
  local_68 = 0x6e44564d6e575f53;
  local_60 = 0x576a48545b585747;
  local_58 = 0x535d45675d57535e;
  local_50 = 0x675a42444550416b;
  local_48 = 0x415e5543;
  local_44 = 0x52;
  local_c = 0;
  while( true ) {
    if (0x24 < local_c) {
      return 2;
    }
    if (((int)*(char *)((long)&local_38 + (long)local_c) ^ (uint)*(byte *)(param_1 + local_c)) !=
        (int)*(char *)((long)&local_68 + (long)local_c)) break;
    local_c = local_c + 1;
  }
  return 1;
}

local_38のstack strings

  local_38 = 0x3131393431333637;
  local_30 = 0x3435313837393235;
  local_28 = 0x3635313836343636;
  local_20 = 0x3834303131353334;
  local_18 = 0x34323435;
  local_14 = 0x37;

と,local_68のstack strings

  local_68 = 0x6e44564d6e575f53;
  local_60 = 0x576a48545b585747;
  local_58 = 0x535d45675d57535e;
  local_50 = 0x675a42444550416b;
  local_48 = 0x415e5543;
  local_44 = 0x52;

を xor すれば flag みたいです。

本当?
main関数に戻ってみる

  int iVar1;
  size_t sVar2;
  char local_78 [48];
  char local_48 [5];
  char acStack67 [37];
  char acStack30 [14];
  int local_10;
  int local_c;

  printf("input flag : ");
  __isoc99_scanf(&DAT_00101dc2,local_48);
  sVar2 = strlen(local_48);
  if (((sVar2 != 0x2b) || (iVar1 = strncmp(local_48,"FLAG{",5), iVar1 != 0)) ||
     (iVar1 = strcmp(acStack30,"}"), iVar1 != 0)) {
    puts("Incorrect");
    return 1;
  }
  strncpy(local_78,acStack67,0x25);
  local_c = 0;
  do {
    if (0x13 < local_c) {
      return 0;
    }
    local_10 = check(local_c,local_78,local_78);

check関数に渡してる local_78 は,acStack67のコピーだけど。。。
acStack67への代入はどこにも無い。

型を変更して,デコンパイル誤りを正す。

  int iVar1;
  size_t sVar2;
  char local_78 [48];
  char local_48 [48];
  int local_c;

  printf("input flag : ");
  __isoc99_scanf(&DAT_00101dc2,local_48);
  sVar2 = strlen(local_48);
  if (((sVar2 != 0x2b) || (iVar1 = strncmp(local_48,"FLAG{",5), iVar1 != 0)) ||
     (iVar1 = strcmp(local_48 + 0x2a,"}"), iVar1 != 0)) {
    puts("Incorrect");
    return 1;
  }
  strncpy(local_78,local_48 + 5,0x25);

check関数(check_13関数)に渡すのは,"FLAG{"の 5バイトを除いた 0x25バイトですね。

stack strings のところの逆アセンブルはこんな感じ

        0010139c 48 b8 37 ..     MOV        RAX,0x3131393431333637
        001013a6 48 ba 35 ..     MOV        RDX,0x3435313837393235
        001013b0 48 89 45 d0     MOV        qword ptr [RBP + local_38],RAX
        001013b4 48 89 55 d8     MOV        qword ptr [RBP + local_30],RDX
        001013b8 48 b8 36 ..     MOV        RAX,0x3635313836343636
        001013c2 48 ba 34 ..     MOV        RDX,0x3834303131353334
        001013cc 48 89 45 e0     MOV        qword ptr [RBP + local_28],RAX
        001013d0 48 89 55 e8     MOV        qword ptr [RBP + local_20],RDX
        001013d4 c7 45 f0 ..     MOV        dword ptr [RBP + local_18],0x34323435
        001013db c6 45 f4 37     MOV        byte ptr [RBP + local_14],0x37
        001013df 48 b8 53 ..     MOV        RAX,0x6e44564d6e575f53
        001013e9 48 ba 47 ..     MOV        RDX,0x576a48545b585747
        001013f3 48 89 45 a0     MOV        qword ptr [RBP + local_68],RAX
        001013f7 48 89 55 a8     MOV        qword ptr [RBP + local_60],RDX
        001013fb 48 b8 5e ..     MOV        RAX,0x535d45675d57535e
        00101405 48 ba 6b ..     MOV        RDX,0x675a42444550416b
        0010140f 48 89 45 b0     MOV        qword ptr [RBP + local_58],RAX
        00101413 48 89 55 b8     MOV        qword ptr [RBP + local_50],RDX
        00101417 c7 45 c0 ..     MOV        dword ptr [RBP + local_48],0x415e5543
        0010141e c6 45 c4 52     MOV        byte ptr [RBP + local_44],0x52

ソルバー ( Ghidra Script ) を作ってみる

def qword_parse(value):
    return([(value & 0x00000000000000ff) / 0x1,(value & 0x000000000000ff00) / 0x100,(value & 0x0000000000ff0000) / 0x10000,(value & 0x00000000ff000000) / 0x1000000,(value & 0x000000ff00000000) / 0x100000000,(value & 0x0000ff0000000000) / 0x10000000000,(value & 0x00ff000000000000) / 0x1000000000000,(value & 0xff00000000000000) / 0x100000000000000])

def dword_parse(value):
    return([(value & 0x000000ff) / 0x1,(value & 0x0000ff00) / 0x100,(value & 0x00ff0000) / 0x10000,(value & 0xff000000) / 0x1000000])

def word_parse(value):
    return([(value & 0x00ff) / 0x1,(value & 0xff00) / 0x100])

# step 1 local_38 extract
local_38 = []

addr = toAddr(0x0010139c)
inst = getInstructionAt(addr)
local_38.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013a6)
inst = getInstructionAt(addr)
local_38.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013b8)
inst = getInstructionAt(addr)
local_38.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013c2)
inst = getInstructionAt(addr)
local_38.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013d4)
inst = getInstructionAt(addr)
local_38.extend(dword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013db)
inst = getInstructionAt(addr)
local_38.append(inst.getOpObjects(1)[0].getValue())

#print(local_38)
#print(''.join(map(chr,local_38)))

# step 2 local_68 extract
local_68 = []

addr = toAddr(0x001013df)
inst = getInstructionAt(addr)
local_68.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013e9)
inst = getInstructionAt(addr)
local_68.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013fb)
inst = getInstructionAt(addr)
local_68.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x00101405)
inst = getInstructionAt(addr)
local_68.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x00101417)
inst = getInstructionAt(addr)
local_68.extend(dword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x0010141e)
inst = getInstructionAt(addr)
local_68.append(inst.getOpObjects(1)[0].getValue())

#print(local_68)
#print(''.join(map(chr,local_68)))

# step 2 xor
ans = []
i = 0
while i < 0x25:
    ans.append(local_38[i] ^ local_68[i])
    i = i + 1

#print(ans)
print(''.join(map(chr,ans)))

もっと美しくコードを書ければいいのに。。。

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

WaniCTF Complex Writeup Using Ghidra

Ghidraを勉強した記録として残しておく。
stack strings の問題だけど byte だけではなく,qword, dword, word も登場する。

complexの入手先

表層解析で得られる文字列 Incorrect を検索
main関数だ

undefined8 main(void)
{
  int iVar1;
  size_t sVar2;
  char local_78 [48];
  char local_48 [5];
  char acStack67 [37];
  char acStack30 [14];
  int local_10;
  int local_c;

  printf("input flag : ");
  __isoc99_scanf(&DAT_00101dc2,local_48);
  sVar2 = strlen(local_48);
  if (((sVar2 != 0x2b) || (iVar1 = strncmp(local_48,"FLAG{",5), iVar1 != 0)) ||
     (iVar1 = strcmp(acStack30,"}"), iVar1 != 0)) {
    puts("Incorrect");
    return 1;
  }
  strncpy(local_78,acStack67,0x25);
  local_c = 0;
  do {
    if (0x13 < local_c) {
      return 0;
    }
    local_10 = check(local_c,local_78,local_78);
    if (local_10 != 0) {
      if (local_10 == 1) {
        puts("Incorrect");
        return 1;
      }
      if (local_10 == 2) {
        printf("Correct! Flag is %s\n",local_48);
        return 0;
      }
    }
    local_c = local_c + 1;
  } while( true );
}

check関数のリターンが 2 だと Correct! になるようだ。

check関数

void check(undefined4 param_1,undefined8 param_2)
{
  switch(param_1) {
  case 0:
    check_0(param_2);
    break;
  case 1:
    check_1(param_2);
    break;
  case 2:
    check_2(param_2);
    break;
  case 3:
    check_3(param_2);
    break;
  case 4:
    check_4(param_2);
    break;
  case 5:
    check_5(param_2);
    break;
  case 6:
    check_6(param_2);
    break;
  case 7:
    check_7(param_2);
    break;
  case 8:
    check_8(param_2);
    break;
  case 9:
    check_9(param_2);
    break;
  case 10:
    check_10(param_2);
    break;
  case 0xb:
    check_11(param_2);
    break;
  case 0xc:
    check_12(param_2);
    break;
  case 0xd:
    check_13(param_2);
    break;
  case 0xe:
    check_14(param_2);
    break;
  case 0xf:
    check_15(param_2);
    break;
  case 0x10:
    check_16(param_2);
    break;
  case 0x11:
    check_17(param_2);
    break;
  case 0x12:
    check_18(param_2);
    break;
  case 0x13:
    check_19(param_2);
  }
  return;
}

心を折りにきてるな。負けるもんか。(正直,心折れそう)
一つずつ落ち着いてみていく。
check_13関数だけ 2 を返してる

undefined8 check_13(long param_1)

{
  undefined8 local_68;
  undefined8 local_60;
  undefined8 local_58;
  undefined8 local_50;
  undefined4 local_48;
  undefined local_44;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  undefined8 local_20;
  undefined4 local_18;
  undefined local_14;
  int local_c;

  local_38 = 0x3131393431333637;
  local_30 = 0x3435313837393235;
  local_28 = 0x3635313836343636;
  local_20 = 0x3834303131353334;
  local_18 = 0x34323435;
  local_14 = 0x37;
  local_68 = 0x6e44564d6e575f53;
  local_60 = 0x576a48545b585747;
  local_58 = 0x535d45675d57535e;
  local_50 = 0x675a42444550416b;
  local_48 = 0x415e5543;
  local_44 = 0x52;
  local_c = 0;
  while( true ) {
    if (0x24 < local_c) {
      return 2;
    }
    if (((int)*(char *)((long)&local_38 + (long)local_c) ^ (uint)*(byte *)(param_1 + local_c)) !=
        (int)*(char *)((long)&local_68 + (long)local_c)) break;
    local_c = local_c + 1;
  }
  return 1;
}

local_38のstack strings

  local_38 = 0x3131393431333637;
  local_30 = 0x3435313837393235;
  local_28 = 0x3635313836343636;
  local_20 = 0x3834303131353334;
  local_18 = 0x34323435;
  local_14 = 0x37;

と,local_68のstack strings

  local_68 = 0x6e44564d6e575f53;
  local_60 = 0x576a48545b585747;
  local_58 = 0x535d45675d57535e;
  local_50 = 0x675a42444550416b;
  local_48 = 0x415e5543;
  local_44 = 0x52;

を xor すれば flag みたいです。

本当?
main関数に戻ってみる

  int iVar1;
  size_t sVar2;
  char local_78 [48];
  char local_48 [5];
  char acStack67 [37];
  char acStack30 [14];
  int local_10;
  int local_c;

  printf("input flag : ");
  __isoc99_scanf(&DAT_00101dc2,local_48);
  sVar2 = strlen(local_48);
  if (((sVar2 != 0x2b) || (iVar1 = strncmp(local_48,"FLAG{",5), iVar1 != 0)) ||
     (iVar1 = strcmp(acStack30,"}"), iVar1 != 0)) {
    puts("Incorrect");
    return 1;
  }
  strncpy(local_78,acStack67,0x25);
  local_c = 0;
  do {
    if (0x13 < local_c) {
      return 0;
    }
    local_10 = check(local_c,local_78,local_78);

check関数に渡してる local_78 は,acStack67のコピーだけど。。。
acStack67への代入はどこにも無い。

型を変更して,デコンパイル誤りを正す。

  int iVar1;
  size_t sVar2;
  char local_78 [48];
  char local_48 [48];
  int local_c;

  printf("input flag : ");
  __isoc99_scanf(&DAT_00101dc2,local_48);
  sVar2 = strlen(local_48);
  if (((sVar2 != 0x2b) || (iVar1 = strncmp(local_48,"FLAG{",5), iVar1 != 0)) ||
     (iVar1 = strcmp(local_48 + 0x2a,"}"), iVar1 != 0)) {
    puts("Incorrect");
    return 1;
  }
  strncpy(local_78,local_48 + 5,0x25);

check関数(check_13関数)に渡すのは,"FLAG{"の 5バイトを除いた 0x25バイトですね。

stack strings のところの逆アセンブルはこんな感じ

        0010139c 48 b8 37 ..     MOV        RAX,0x3131393431333637
        001013a6 48 ba 35 ..     MOV        RDX,0x3435313837393235
        001013b0 48 89 45 d0     MOV        qword ptr [RBP + local_38],RAX
        001013b4 48 89 55 d8     MOV        qword ptr [RBP + local_30],RDX
        001013b8 48 b8 36 ..     MOV        RAX,0x3635313836343636
        001013c2 48 ba 34 ..     MOV        RDX,0x3834303131353334
        001013cc 48 89 45 e0     MOV        qword ptr [RBP + local_28],RAX
        001013d0 48 89 55 e8     MOV        qword ptr [RBP + local_20],RDX
        001013d4 c7 45 f0 ..     MOV        dword ptr [RBP + local_18],0x34323435
        001013db c6 45 f4 37     MOV        byte ptr [RBP + local_14],0x37
        001013df 48 b8 53 ..     MOV        RAX,0x6e44564d6e575f53
        001013e9 48 ba 47 ..     MOV        RDX,0x576a48545b585747
        001013f3 48 89 45 a0     MOV        qword ptr [RBP + local_68],RAX
        001013f7 48 89 55 a8     MOV        qword ptr [RBP + local_60],RDX
        001013fb 48 b8 5e ..     MOV        RAX,0x535d45675d57535e
        00101405 48 ba 6b ..     MOV        RDX,0x675a42444550416b
        0010140f 48 89 45 b0     MOV        qword ptr [RBP + local_58],RAX
        00101413 48 89 55 b8     MOV        qword ptr [RBP + local_50],RDX
        00101417 c7 45 c0 ..     MOV        dword ptr [RBP + local_48],0x415e5543
        0010141e c6 45 c4 52     MOV        byte ptr [RBP + local_44],0x52

ソルバー ( Ghidra Script ) を作ってみる

def qword_parse(value):
    return([(value & 0x00000000000000ff) / 0x1,(value & 0x000000000000ff00) / 0x100,(value & 0x0000000000ff0000) / 0x10000,(value & 0x00000000ff000000) / 0x1000000,(value & 0x000000ff00000000) / 0x100000000,(value & 0x0000ff0000000000) / 0x10000000000,(value & 0x00ff000000000000) / 0x1000000000000,(value & 0xff00000000000000) / 0x100000000000000])

def dword_parse(value):
    return([(value & 0x000000ff) / 0x1,(value & 0x0000ff00) / 0x100,(value & 0x00ff0000) / 0x10000,(value & 0xff000000) / 0x1000000])

def word_parse(value):
    return([(value & 0x00ff) / 0x1,(value & 0xff00) / 0x100])

# step 1 local_38 extract
local_38 = []

addr = toAddr(0x0010139c)
inst = getInstructionAt(addr)
local_38.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013a6)
inst = getInstructionAt(addr)
local_38.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013b8)
inst = getInstructionAt(addr)
local_38.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013c2)
inst = getInstructionAt(addr)
local_38.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013d4)
inst = getInstructionAt(addr)
local_38.extend(dword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013db)
inst = getInstructionAt(addr)
local_38.append(inst.getOpObjects(1)[0].getValue())

#print(local_38)
#print(''.join(map(chr,local_38)))

# step 2 local_68 extract
local_68 = []

addr = toAddr(0x001013df)
inst = getInstructionAt(addr)
local_68.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013e9)
inst = getInstructionAt(addr)
local_68.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013fb)
inst = getInstructionAt(addr)
local_68.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x00101405)
inst = getInstructionAt(addr)
local_68.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x00101417)
inst = getInstructionAt(addr)
local_68.extend(dword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x0010141e)
inst = getInstructionAt(addr)
local_68.append(inst.getOpObjects(1)[0].getValue())

#print(local_68)
#print(''.join(map(chr,local_68)))

# step 3 xor
ans = []
i = 0
while i < 0x25:
    ans.append(local_38[i] ^ local_68[i])
    i = i + 1

#print(ans)
print(''.join(map(chr,ans)))

もっと美しくコードを書ければいいのに。。。

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

WaniCTF complex をGhidraで静的解析してみた

Ghidraを勉強した記録として残しておく。
stack strings の問題だけど byte だけではなく,qword, dword, word も登場する。

complexの入手先

表層解析で得られる文字列 Incorrect を検索
main関数だ

undefined8 main(void)
{
  int iVar1;
  size_t sVar2;
  char local_78 [48];
  char local_48 [5];
  char acStack67 [37];
  char acStack30 [14];
  int local_10;
  int local_c;

  printf("input flag : ");
  __isoc99_scanf(&DAT_00101dc2,local_48);
  sVar2 = strlen(local_48);
  if (((sVar2 != 0x2b) || (iVar1 = strncmp(local_48,"FLAG{",5), iVar1 != 0)) ||
     (iVar1 = strcmp(acStack30,"}"), iVar1 != 0)) {
    puts("Incorrect");
    return 1;
  }
  strncpy(local_78,acStack67,0x25);
  local_c = 0;
  do {
    if (0x13 < local_c) {
      return 0;
    }
    local_10 = check(local_c,local_78,local_78);
    if (local_10 != 0) {
      if (local_10 == 1) {
        puts("Incorrect");
        return 1;
      }
      if (local_10 == 2) {
        printf("Correct! Flag is %s\n",local_48);
        return 0;
      }
    }
    local_c = local_c + 1;
  } while( true );
}

check関数のリターンが 2 だと Correct! になるようだ。

check関数

void check(undefined4 param_1,undefined8 param_2)
{
  switch(param_1) {
  case 0:
    check_0(param_2);
    break;
  case 1:
    check_1(param_2);
    break;
  case 2:
    check_2(param_2);
    break;
  case 3:
    check_3(param_2);
    break;
  case 4:
    check_4(param_2);
    break;
  case 5:
    check_5(param_2);
    break;
  case 6:
    check_6(param_2);
    break;
  case 7:
    check_7(param_2);
    break;
  case 8:
    check_8(param_2);
    break;
  case 9:
    check_9(param_2);
    break;
  case 10:
    check_10(param_2);
    break;
  case 0xb:
    check_11(param_2);
    break;
  case 0xc:
    check_12(param_2);
    break;
  case 0xd:
    check_13(param_2);
    break;
  case 0xe:
    check_14(param_2);
    break;
  case 0xf:
    check_15(param_2);
    break;
  case 0x10:
    check_16(param_2);
    break;
  case 0x11:
    check_17(param_2);
    break;
  case 0x12:
    check_18(param_2);
    break;
  case 0x13:
    check_19(param_2);
  }
  return;
}

心を折りにきてるな。負けるもんか。(正直,心折れそう)
一つずつ落ち着いてみていく。
check_13関数だけ 2 を返してる

undefined8 check_13(long param_1)

{
  undefined8 local_68;
  undefined8 local_60;
  undefined8 local_58;
  undefined8 local_50;
  undefined4 local_48;
  undefined local_44;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  undefined8 local_20;
  undefined4 local_18;
  undefined local_14;
  int local_c;

  local_38 = 0x3131393431333637;
  local_30 = 0x3435313837393235;
  local_28 = 0x3635313836343636;
  local_20 = 0x3834303131353334;
  local_18 = 0x34323435;
  local_14 = 0x37;
  local_68 = 0x6e44564d6e575f53;
  local_60 = 0x576a48545b585747;
  local_58 = 0x535d45675d57535e;
  local_50 = 0x675a42444550416b;
  local_48 = 0x415e5543;
  local_44 = 0x52;
  local_c = 0;
  while( true ) {
    if (0x24 < local_c) {
      return 2;
    }
    if (((int)*(char *)((long)&local_38 + (long)local_c) ^ (uint)*(byte *)(param_1 + local_c)) !=
        (int)*(char *)((long)&local_68 + (long)local_c)) break;
    local_c = local_c + 1;
  }
  return 1;
}

local_38のstack strings

  local_38 = 0x3131393431333637;
  local_30 = 0x3435313837393235;
  local_28 = 0x3635313836343636;
  local_20 = 0x3834303131353334;
  local_18 = 0x34323435;
  local_14 = 0x37;

と,local_68のstack strings

  local_68 = 0x6e44564d6e575f53;
  local_60 = 0x576a48545b585747;
  local_58 = 0x535d45675d57535e;
  local_50 = 0x675a42444550416b;
  local_48 = 0x415e5543;
  local_44 = 0x52;

を xor すれば flag みたいです。

本当?
main関数に戻ってみる

  int iVar1;
  size_t sVar2;
  char local_78 [48];
  char local_48 [5];
  char acStack67 [37];
  char acStack30 [14];
  int local_10;
  int local_c;

  printf("input flag : ");
  __isoc99_scanf(&DAT_00101dc2,local_48);
  sVar2 = strlen(local_48);
  if (((sVar2 != 0x2b) || (iVar1 = strncmp(local_48,"FLAG{",5), iVar1 != 0)) ||
     (iVar1 = strcmp(acStack30,"}"), iVar1 != 0)) {
    puts("Incorrect");
    return 1;
  }
  strncpy(local_78,acStack67,0x25);
  local_c = 0;
  do {
    if (0x13 < local_c) {
      return 0;
    }
    local_10 = check(local_c,local_78,local_78);

check関数に渡してる local_78 は,acStack67のコピーだけど。。。
acStack67への代入はどこにも無い。

型を変更して,デコンパイル誤りを正す。

  int iVar1;
  size_t sVar2;
  char local_78 [48];
  char local_48 [48];
  int local_c;

  printf("input flag : ");
  __isoc99_scanf(&DAT_00101dc2,local_48);
  sVar2 = strlen(local_48);
  if (((sVar2 != 0x2b) || (iVar1 = strncmp(local_48,"FLAG{",5), iVar1 != 0)) ||
     (iVar1 = strcmp(local_48 + 0x2a,"}"), iVar1 != 0)) {
    puts("Incorrect");
    return 1;
  }
  strncpy(local_78,local_48 + 5,0x25);

check関数(check_13関数)に渡すのは,"FLAG{"の 5バイトを除いた 0x25バイトですね。

stack strings のところの逆アセンブルはこんな感じ

        0010139c 48 b8 37 ..     MOV        RAX,0x3131393431333637
        001013a6 48 ba 35 ..     MOV        RDX,0x3435313837393235
        001013b0 48 89 45 d0     MOV        qword ptr [RBP + local_38],RAX
        001013b4 48 89 55 d8     MOV        qword ptr [RBP + local_30],RDX
        001013b8 48 b8 36 ..     MOV        RAX,0x3635313836343636
        001013c2 48 ba 34 ..     MOV        RDX,0x3834303131353334
        001013cc 48 89 45 e0     MOV        qword ptr [RBP + local_28],RAX
        001013d0 48 89 55 e8     MOV        qword ptr [RBP + local_20],RDX
        001013d4 c7 45 f0 ..     MOV        dword ptr [RBP + local_18],0x34323435
        001013db c6 45 f4 37     MOV        byte ptr [RBP + local_14],0x37
        001013df 48 b8 53 ..     MOV        RAX,0x6e44564d6e575f53
        001013e9 48 ba 47 ..     MOV        RDX,0x576a48545b585747
        001013f3 48 89 45 a0     MOV        qword ptr [RBP + local_68],RAX
        001013f7 48 89 55 a8     MOV        qword ptr [RBP + local_60],RDX
        001013fb 48 b8 5e ..     MOV        RAX,0x535d45675d57535e
        00101405 48 ba 6b ..     MOV        RDX,0x675a42444550416b
        0010140f 48 89 45 b0     MOV        qword ptr [RBP + local_58],RAX
        00101413 48 89 55 b8     MOV        qword ptr [RBP + local_50],RDX
        00101417 c7 45 c0 ..     MOV        dword ptr [RBP + local_48],0x415e5543
        0010141e c6 45 c4 52     MOV        byte ptr [RBP + local_44],0x52

ソルバー ( Ghidra Script ) を作ってみる

def qword_parse(value):
    return([(value & 0x00000000000000ff) / 0x1,(value & 0x000000000000ff00) / 0x100,(value & 0x0000000000ff0000) / 0x10000,(value & 0x00000000ff000000) / 0x1000000,(value & 0x000000ff00000000) / 0x100000000,(value & 0x0000ff0000000000) / 0x10000000000,(value & 0x00ff000000000000) / 0x1000000000000,(value & 0xff00000000000000) / 0x100000000000000])

def dword_parse(value):
    return([(value & 0x000000ff) / 0x1,(value & 0x0000ff00) / 0x100,(value & 0x00ff0000) / 0x10000,(value & 0xff000000) / 0x1000000])

def word_parse(value):
    return([(value & 0x00ff) / 0x1,(value & 0xff00) / 0x100])

# step 1 local_38 extract
local_38 = []

addr = toAddr(0x0010139c)
inst = getInstructionAt(addr)
local_38.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013a6)
inst = getInstructionAt(addr)
local_38.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013b8)
inst = getInstructionAt(addr)
local_38.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013c2)
inst = getInstructionAt(addr)
local_38.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013d4)
inst = getInstructionAt(addr)
local_38.extend(dword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013db)
inst = getInstructionAt(addr)
local_38.append(inst.getOpObjects(1)[0].getValue())

#print(local_38)
#print(''.join(map(chr,local_38)))

# step 2 local_68 extract
local_68 = []

addr = toAddr(0x001013df)
inst = getInstructionAt(addr)
local_68.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013e9)
inst = getInstructionAt(addr)
local_68.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x001013fb)
inst = getInstructionAt(addr)
local_68.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x00101405)
inst = getInstructionAt(addr)
local_68.extend(qword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x00101417)
inst = getInstructionAt(addr)
local_68.extend(dword_parse(inst.getOpObjects(1)[0].getValue()))

addr = toAddr(0x0010141e)
inst = getInstructionAt(addr)
local_68.append(inst.getOpObjects(1)[0].getValue())

#print(local_68)
#print(''.join(map(chr,local_68)))

# step 2 xor
ans = []
i = 0
while i < 0x25:
    ans.append(local_38[i] ^ local_68[i])
    i = i + 1

#print(ans)
print(''.join(map(chr,ans)))

もっと美しくコードを書ければいいのに。。。

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

色々めんどくさいので作りながら学ぶDjango2

Djangoにアプリを作っていきます。

プロジェクト下にCRMというアプリを作成していきます。

$ python manage.py startapp cms

キャプチャ.PNG

上記のCMSというディレクトリが作成されました。

モデルの作成

DBの定義にしたいDBモデルをcms/models.pyに入れていきます。

from django.db import models


class Book(models.Model):
    """書籍"""
    name = models.CharField('書籍名', max_length=255)
    publisher = models.CharField('出版社', max_length=255, blank=True)
    page = models.IntegerField('ページ数', blank=True, default=0)

    def __str__(self):
        return self.name


class Impression(models.Model):
    """感想"""
    book = models.ForeignKey(Book, verbose_name='書籍', related_name='impressions', on_delete=models.CASCADE)
    comment = models.TextField('コメント', blank=True)

    def __str__(self):
        return self.comment


感想は書籍に紐づく、モデルとしています。

モデルを有効化します

CMSをインストールしたことをプロジェクトに教えてあげる必要があります。

これを applepie/settings.py の INSTALLED_APPS の最後に 'cms.apps.CmsConfig', という文字列で追加します。

cms/apps.pyを一度開いてみると、cmsconfigが定義されています。

キャプチャ.PNG

確認した上でこの設定を親に反映させていきます。

キャプチャ.PNG

以下コマンドを入力してマイグレファイルを作成していきます

>python manage.py makemigrations cms
Migrations for 'cms':
  cms\migrations\0001_initial.py
    - Create model Book
    - Create model Impression

このファイルがどんなSQLなのか中身を確認します。

python manage.py sqlmigrate cms 0001
BEGIN;
--
-- Create model Book
--
CREATE TABLE "cms_book" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(255) NOT NULL, "publisher" varchar(255) NOT NULL, "page" integer NOT NULL);
--
-- Create model Impression
--
CREATE TABLE "cms_impression" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "comment" text NOT NULL, "book_id" integer NOT NULL REFERENCES "cms_book" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "cms_impression_book_id_b2966102" ON "cms_impression" ("book_id");
COMMIT;

dbに反映していないファイルを反映させます。

>python manage.py migrate cms
Operations to perform:
  Apply all migrations: cms
Running migrations:
  Applying cms.0001_initial... OK

ページの確認

python manage.py runserverでサーバーを起動させます。
起動後http://127.0.0.1:8000/admin/ にブラウザでアクセスします。

初回に入力したスーパーユーザーでログインを行ってください。

キャプチャ.PNG

ログインすると下のような状態になっています。
ユーザーとグループしかない状態です。
キャプチャ.PNG

cmsモデルをadmin上で編集できるようにします

cms/admin.pyに管理サイト表示したいモデルを追加します。

from django.contrib import admin
from cms.models import Book, Impression

admin.site.register(Book)
admin.site.register(Impression)

キャプチャ.PNG

CMSの項目が追加されているのが確認ができました。

管理サイトの一覧ページをカスタマイズ

cms/admin.pyを修正します

from django.contrib import admin

from cms.models import Book, Impression

# admin.site.register(Book)
# admin.site.register(Impression)


class BookAdmin(admin.ModelAdmin):
    list_display = ('id', 'name', 'publisher', 'page',)  # 一覧に出したい項目
    list_display_links = ('id', 'name',)  # 修正リンクでクリックできる項目


admin.site.register(Book, BookAdmin)


class ImpressionAdmin(admin.ModelAdmin):
    list_display = ('id', 'comment',)
    list_display_links = ('id', 'comment',)
    raw_id_fields = ('book',)   # 外部キーをプルダウンにしない(データ件数が増加時のタイムアウトを予防)


admin.site.register(Impression, ImpressionAdmin)


キャプチャ.PNG

こんな感じになりました。

見た目を良くします

Bootstrapを使用します
*jQueryがない場合はインスコする必要あり。

applepie
└── cms
    └── static
        └── cms
            ├── css
            │   ├── bootstrap.min.css
            │   └── bootstrap.min.css.map
            └── js
                ├── bootstrap.bundle.min.js
                ├── bootstrap.bundle.min.js.map
                ├── jquery-3.4.1.min.js
                └── jquery-3.4.1.min.map

上記のようにファイルを配置します。

Bootstrapを入れ込みます

applepie>pip install django-bootstrap4
Collecting django-bootstrap4
  Downloading django_bootstrap4-2.3.1-py3-none-any.whl (24 kB)
Collecting beautifulsoup4<5.0.0,>=4.8.0
  Downloading beautifulsoup4-4.9.3-py3-none-any.whl (115 kB)
     |████████████████████████████████| 115 kB 939 kB/s
Requirement already satisfied: django<4.0,>=2.2 in c:\users\8140\appdata\local\programs\python\python39\lib\site-packages (from django-bootstrap4) (3.1)
Collecting soupsieve>1.2; python_version >= "3.0"
  Downloading soupsieve-2.2-py3-none-any.whl (33 kB)
Requirement already satisfied: asgiref~=3.2.10 in c:\users\8140\appdata\local\programs\python\python39\lib\site-packages (from django<4.0,>=2.2->django-bootstrap4) (3.2.10)
Requirement already satisfied: sqlparse>=0.2.2 in c:\users\8140\appdata\local\programs\python\python39\lib\site-packages (from django<4.0,>=2.2->django-bootstrap4) (0.4.1)
Requirement already satisfied: pytz in c:\users\8140\appdata\local\programs\python\python39\lib\site-packages (from django<4.0,>=2.2->django-bootstrap4) (2021.1)
Installing collected packages: soupsieve, beautifulsoup4, django-bootstrap4
Successfully installed beautifulsoup4-4.9.3 django-bootstrap4-2.3.1 soupsieve-2.2

しっかり入ったことが確認できました

applepie>pip freeze -l
appdirs==1.4.4
asgiref==3.2.10
beautifulsoup4==4.9.3
distlib==0.3.1
Django==3.1
django-bootstrap4==2.3.1
django-cors-headers==3.4.0
django-templated-mail==1.1.1
djangorestframework==3.11.1
djoser==2.0.3
filelock==3.0.12
pathtools==0.1.2
Pillow==8.1.0
pytz==2021.1
six==1.15.0
soupsieve==2.2
sqlparse==0.4.1
virtualenv==20.4.2
watchdog==0.10.3

applepie/setting.pyのINSTALLED_APPS に 'bootstrap4' を追加します。

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'cms.apps.CmsConfig',   # cms アプリケーション
    'bootstrap4',           # django-bootstrap4
)

CRUD

ざっとつくります。
ファンクションが必要なので、`cms/views.py' にひな形を作ります。

登録、修正は編集としてひとまとめにしています

from django.shortcuts import render
from django.http import HttpResponse


def book_list(request):
    """書籍の一覧"""
    return HttpResponse('書籍の一覧')


def book_edit(request, book_id=None):
    """書籍の編集"""
    return HttpResponse('書籍の編集')


def book_del(request, book_id):
    """書籍の削除"""
    return HttpResponse('書籍の削除')

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

1年間でpythonで収益化してみた

今日からpythonの勉強を始める
目標としてはこの1年で自分で案件を獲得し収益を生み出すこと

多くのサイトを閲覧しこの1年の大まかな計画を立てる
その中でこのサイトの手順が最も有効だと考えた

【初心者必見】プログラミング未経験から3年間のpython学習ロードマップ

基本的にこの手順に乗っ取って頑張ってみようと思う

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

1年間でpython収益化してみた

今日からpythonの勉強を始める
目標としてはこの1年で自分で案件を獲得し収益を生み出すこと

多くのサイトを閲覧しこの1年の大まかな計画を立てる
その中でこのサイトの手順が最も有効だと考えた

【初心者必見】プログラミング未経験から3年間のpython学習ロードマップ

基本的にこの手順に乗っ取って頑張ってみようと思う

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

PuLPで変数を沢山作るときはLpVariable.dictsを使おう

最適化をやっていると、変数を一度に沢山作る機会があります。pythonで最適化をやる際におそらく一番使われるPuLPで、その書き方を強調して書いてあるものが少なかったので、ご参考になればと思い書いときます。

3行サマリ

  • 変数はpulp.LpVariablle.dictsで作成する
  • 式の表現はlpSumを使う

がおすすめです。

LpVariable.dictsを使うべき理由

書き方がいくつかあるなかで、なぜLpvariable.dictsが良いのかというと、、、

理由1:リストの何番目とか分からなくなるので

他の代表的な書き方に「for文や内包表記で変数のリストを作成する」というものがあると思います。

でも、商品Aにだけこの制約を加えたい、だとか地点Bから地点Cに行くことだけは避けたい、みたいな制約を入れたいときってありますよね。リストで作成していると、その商品や地点ってリストの何番目?となるわけです。

一説によると人間が短期記憶できる数は7個程度とも言われていますし1、インドのPuri族には数を表す概念は1,2,沢山のみだそうです2。つまり数という概念は人間にとって分かりやすいものではないというわけです。私も5を超えるとだいたい途中で分からなくなって2~3回数え直します

ということで、リストの何番目かを考えるのはいまの人類には高度すぎるため、なるべく苦手なことに脳を使わないですむように辞書を用いるのが良いと思います。

理由2:公式のexampleもそれを使っているので

Docsにある例もそうですし、githubにおいてある例もそうなっています。

自分もそこまで沢山知っているわけではないですが、最適化の文脈だと辞書で作るのがひとつのスタンダードであると思います。例えばgurobiのexampleもそうでした3。長いものには巻かれときましょう。

ということで、じゃあ具体的にこう書くのがいいだろう、と私が考えているものを例を用いて見ていきます。

標準的なナップサック問題を解きます。題材は駄菓子にしましょう。遠足でよくある、300円までで一番魅力的な駄菓子パーティーを組む問題です。数式で書くと

\begin{align}
\max ~ & \sum_{i\in I}Value_i \cdot x_i \\
s.t. ~ & \sum_{i\in I}Price_i \cdot x_i <= 300 \\
& x_i \in \{0,1\}
\end{align}

です。$x_i$はおかし$i$を買うならば1、買わないならば0をとる変数とします。金額の合計が300円を超えないように、価値が最大になるように買う商品を選ぶわけです。同じおかしを複数個買うようなパーティーが最強であるわけがないので、$x_i$は整数でなくてBinaryにしています。これを以降ではKnapSnack問題と呼びます。

データの取得

価格が分からないと問題が解けないので、そのデータを取得します。みんな大好き、やおきんさんのオンラインショップからスクレイピングします4。ここは記事の本題とは関係ないので、お急ぎの型は最適化パートまで飛んでも大丈夫です。

import requests
from bs4 import BeautifulSoup
import numpy as np
import pandas as pd

df = pd.DataFrame()
for i in range(19):
    url = f'https://dagasiya.com/shop/shopbrand.html?page={i}&search=&sort=&money1=&money2=500&prize1=&company1=&content1=&originalcode1=&category=&subcategory='
    res = requests.get(url)
    soup = BeautifulSoup(res.text, "html.parser")
    name = soup.find_all('p', class_='itemName')
    price = soup.find_all('p', class_='itemPrice')
    df_page = pd.DataFrame({
        'Name': [i.text for i in name],
        'Price': [i.text for i in price]
    })
    df = df.append(df_page)


df = df.drop_duplicates('Name')
df['Value'] = np.abs(np.random.randn(len(df)))
df['Price'] = df['Price'].str[:-1].astype(int)

スクレイピングは本記事の本題ではないので、さっとさらうだけにします5

  • 高すぎる駄菓子はロマンがないので、500円以下のものに絞ります
    • urlのmoney2=500のところが該当します
  • requestsを用いてhtmlを取得します
  • BeautifulSoupを用いてパース(文字列を分かりやすい形に整理)します
  • 商品名と価格に対応するcssのclassをもつ要素をすべて取得します
  • 取得した結果を、pandasのDataFrameに格納します
  • 以上を検索結果ページ数(19ページ)分繰り返します

さらに、最適化の都合で以下の操作を加えます。

  • 同じ商品名のものがあるとどっちがどっちか分からなくなるので、商品名が重複しているものは片方省きます
  • 「一番魅力的なパーティー」の定義は非常に難しい問題ですが、ここでは適当に乱数で個々の商品の価値をつけています
  • 価格が「450円」のような形で取得されたので、円を削除してint型に変形します

できあがったDataFrameがこちらです。

image.png

さて、困ったことに、20個入りなどの単位でしか売ってません。まあそりゃオンラインで駄菓子1個単位で打ってたら商売成り立たないか。。

まあ個数当たりの金額にしたりするのは面倒なので、無視してこのままいきましょう。

最適化の実行

price = df.set_index('Name')['Price'].to_dict()
value = df.set_index('Name')['Value'].to_dict()

まず、最適化に用いる定数を辞書として用意します。別にpandasのまま処理してもいいのだけど、公式のexampleと対応するように辞書にしています。

ここではkeyが商品名で、valueがそれぞれ価格や価値であるような辞書ができます。valueが被っていてイケてないですね。。すみません

では本題のPuLPの部分です。

# 問題の作成
prob = pulp.LpProblem(name='KnapSnack', sense=-1)
# 変数の作成
x = pulp.LpVariable.dicts('x', df['Name'], cat='Binary')
# 目的関数の追加
prob += pulp.lpSum([x[i] * value[i] for i in x.keys()])
# 制約の追加
prob += pulp.lpSum([x[i] * price[i] for i in x.keys()]) <= 30 * 300

# 求解
sol = prob.solve()
# 解けているかを確認
print(pulp.LpStatus[sol])          # Optimal
# 最適解を確認
print(pulp.value(prob.objective))  # 61.21364708355906

1. 問題の作成
2. 変数の作成:'x'が変数の名前、次に辞書のkeyに対応するもの、最後にBinary変数を指定しています。これで、keyが商品名でvalueがPuLPの変数である辞書が作成されます
3. 目的関数の追加:基本的にはlpSumを使うことになるでしょう。リスト内包表記で足し上げる要素を羅列するのをよく見ます
4. 制約の追加:先ほど見たように単位がバグってたので、一クラス分(30人×300円)を買う問題に変更していますw
5. 求解

ということで、シンプルに記述できました。以下で変数の作り方について補足します。

変数の作成

LpVariable.dicts(name, indexs, lowBound=None, upBound=None, cat='Continuous', indexStart=[])

引数は、nameが変数名です。indexsが辞書のkeyになるもので、文字列のリストを渡します。
他は色んなところに書いてあるのと同じなので略します。

結果

そういえば結果です。こいつらが最強のおかしパーティーです。

商品名 価格 ポイント
13 うまい棒鉛筆3種(HB・B・2B)各3本セット + 特典 475 1.958077
19 つけつけペロスティックチョコ 6入り 311 0.360806
2 新サワーコーラグミ 60入り 518 1.042587
18 ペンシルカルパス 20入り 518 0.017585
23 ガブリチュウ・グレープ 20入り 518 0.144169
3 オレンジマーブルガム 24入り 311 0.260376
4 ドラえもんマーブルガム 24入り 311 0.464725
12 もちっときなこ餅 20入り 518 0.310539
20 カジリッチョ サイダー味 20入り 518 0.962775
12 フルーツモンスターレインボー味 12入り 518 0.909126
5 BR 出前寿司セット 1個 385 1.410559
9 BR 富士山と神社 1個 385 0.937255
18 BR スポーツセット 1個 385 0.180035
0 BR お寿司 1個 385 1.340095
1 BR お料理 1個 385 0.490019
12 こんぺいとう 20個入 518 0.438453
14 塩吉原羊かん 12入り 259 1.477365
16 ミニしみチョココーン ミルクチョコ 10入り 432 1.221638
8 ましゅろー 30入り 259 1.519683
21 もちもちきなこ 12入り 518 0.700150
8 変わり味 ギョギョギョガム 20入り 518 0.291298
1 ゾンBのもとガム 18入り 467 1.365611
2 ドラQラのもとガム 18入り 467 0.864054
18 うまい棒お箸 小 とんかつソース 1膳 317 0.239067
19 うまい棒お箸 大 とんかつソース 1膳 317 0.755195
11 うまい棒フォーク タコヤキ 1本 317 0.292592
14 うまい棒フォーク チーズ 1本 317 0.891636
20 フライドポテト 30入り 518 1.119859
18 うまい棒プチマスコット・メンタイ 1個 288 1.381736
19 うまい棒プチマスコット・ソース 1個 288 0.615968
8 うまい棒やさいサラダ味 30本 259 0.981209
10 うまい棒とんかつソース味 30本 259 1.491541

まあ結局価値のところを乱数で出しているので大して意味のある結果にはなっていませんが、やっぱりうまい棒は強いですね。うまい棒最強。あとお寿司がしれっと混ざっているのは笑いました。

おまけ

実は他にもこんなのがあるけど、あまり推奨されてないんじゃないかな、というものです。

自作で変数のリストを作る

散々本記事で書いている通りです。や、手元でサッと作るだけとか、規模が大きくないとかなら問題ないとは思います。

LpVariable.matrix

LpVariable.dictsと同じノリで変数の行列を作ってくれるメソッドがあります。しかし、辞書のほうが良いということで機能としてはあるもののドキュメントには載っていません6

lpDot

lpSumと同じノリで内積($\sum_i x_i p_i$みたいな)を計算してくれる関数が存在します。しかしこれも辞書とは相性が悪いので(リストとかだとi番目同士を掛けるけど、辞書に対しては...)、こちらもドキュメントに載っていません。


  1. まあ色々な条件のもとでの実験の結果で、適切な解釈はだいぶ違うのでしょうが。https://ja.wikipedia.org/wiki/%E3%82%B8%E3%83%A7%E3%83%BC%E3%82%B8%E3%83%BB%E3%83%9F%E3%83%A9%E3%83%BC_(%E5%BF%83%E7%90%86%E5%AD%A6%E8%80%85) 

  2. ちゃちゃっと調べただけなので少し古い論文ですが、こちらなどが参考です。https://opera.repo.nii.ac.jp/?action=repository_action_common_download&item_id=6500&item_no=1&attribute_id=19&file_no=1 

  3. https://www.gurobi.com/documentation/9.1/examples/diet_py.html 

  4. こちらのページです:https://dagasiya.com/shop/shopbrand.html?page=1&search=&sort=&money1=&money2=500&prize1=&company1=&content1=&originalcode1=&category=&subcategory= 

  5. ちなみに余談ですが、やおきんさんのオンラインショップはスクレイピングするのにちょうどいい題材だと感じました(複雑すぎることもないし、きれいなコードで作られている気がしました)。スクレイピング初心者におすすめです。 

  6. ドキュメントにないのはそれはそれで分かりにくいので、載せたうえで非推奨とかって書いてほしいものですけどね。 

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

MLPG:Maximum Likelihood Parameter Generationとは

MLPG

Maximum Likelihood Parameter Generationについて書きます。

DeepLearningなど機械学習では

\begin{align}
 \boldsymbol{y} &= f_\theta (\boldsymbol{x}) \\
 \theta &= argmin_\theta[ Loss(\boldsymbol{y}, \hat{\boldsymbol{y}}) ]
\end{align}

のように、入力$\boldsymbol{x}$から関数$f$を通して$\boldsymbol{y}$を出力するモデルを考えたりします。
このとき教師データ$\hat{\boldsymbol{y}}$と出力$\boldsymbol{y}$間のLossを最小化する関数パラメータ$\theta$を求めるのが学習ブロックです。

ただ、この特徴量$\boldsymbol{x}$は大抵が時刻$t$における特徴量です。
できれば前後の時刻との相関というか時間変化も捉えて学習した方が良さそうですよね???
(とは言え、MLPGでは「生成した特徴量の、時間方向の連続性が破綻してて音質が悪い!」のを防ぐことが目的で、音声の時間変化成分に含まれる個人性を捉えれるかどうかは別だと思います。せいぜい近傍10〜20msしか捉えられないので。)

なので例えば

\begin{align}
\boldsymbol{x} &= [\boldsymbol{x_1}^T, \boldsymbol{x_2}^T, \cdots, \boldsymbol{x_T}^T]^T \\
&(\boldsymbol{x_t} = [x_{1t}, x_{2t}, \cdots, x_{Dt}]^T)
\end{align}

という静的特徴量$\boldsymbol{x}$に動的特徴量を含んだ特徴量$\boldsymbol{X}$

\begin{align}
\boldsymbol{X} &= [\boldsymbol{X_1}^T, \boldsymbol{X_2}^T, \cdots, \boldsymbol{X_T}^T]^T \\
&(\boldsymbol{X_t} = [\boldsymbol{x_t}^T, \Delta\boldsymbol{x_t}^T])
\end{align}

を扱います。
それで

\begin{align}
\boldsymbol{Y} &= f_\theta (\boldsymbol{X}) \\
\theta &= argmin_\theta[ Loss(\boldsymbol{y}, \hat{\boldsymbol{y}}) ] \\
       &= argmin_\theta[ Loss(\boldsymbol{RY}, \hat{\boldsymbol{y}}) ]
\end{align}

というように、静的・動的特徴量$\boldsymbol{X}$を入力したら出力$\boldsymbol{Y}$を求めるモデルを考えます。
そのモデルのパラメータ$\theta$ は $\boldsymbol{RY} (= y)$ と $\hat{\boldsymbol{y}}$のロスを最小化するように学習します。

ここで重要なのは静的・動的特徴量と静的特徴量間の変換行列$\boldsymbol{R}$をどうやって産むかということです。

次節以降、もう少し具体的に必要な行列とかに関する算出方法のメモとコードを記します。

ちょいと数学

ここでやるのは$\boldsymbol{X} = \boldsymbol{Wx}$ なる変換行列$\boldsymbol{W}$ と
$\boldsymbol{x} = \boldsymbol{RX}$ なる変換行列$\boldsymbol{R}$を求めることです。

X = Wx について

※今回は$\Delta\boldsymbol{x}$までしか扱いません。記事や図、コードを書くのが楽なので。さらに$\Delta\Delta\boldsymbol{x}$も考慮したって理屈は同じです。

では本題

スクリーンショット 2021-03-03 23.25.01.png

記事では$\boldsymbol{x}$とか$\boldsymbol{y}$とか入り混じってますが、
大文字と小文字間の変換なので意味は変わりません。ごめんなさい。

図にしてみました!

この行列$\boldsymbol{W}$が言いたいところは

\Delta\boldsymbol{y_t} = -0.5\boldsymbol{y_{t-1}} + 0.5\boldsymbol{y_{t+1}}

だよっていうだけです。

x = RX について

\boldsymbol{R} = (\boldsymbol{W}^T\Sigma^{-1}\boldsymbol{W})^{-1}\boldsymbol{W}^T\boldsymbol{\Sigma}^{-1}

$\boldsymbol{\Sigma}$は$\boldsymbol{X}$の共分散行列です。

「W」算出の実装しまーす

import scipy.sparse
import numpy as np

def computeW(T, D):
  for t in range(T):
    w0 = scipy.sparse.lil_matrix((D, D * T))
    w1 = scipy.sparse.lil_matrix((D, D * T)) 
    w0[0:, t * D:(t + 1) * D] = scipy.sparse.diags(np.ones(D), 0)

    if t >= 1:
      tmp1 = np.zeros(D)
      tmp1.fill(-0.5)
      w1[0:, (t - 1) * D:t * D] = scipy.sparse.diags(tmp1, 0)

    if t < T-1:
      tmp1 = np.zeros(D)
      tmp1.fill(0.5)
      w1[0:, (t + 1) * D:(t + 2) * D] = scipy.sparse.diags(tmp1, 0)


     W_t = scipy.sparse.vstack([w0, w1])

     if t == 0:
       W = W_t
     else:
       W = scipy.sparse.vstack([W, W_t])

  W = scipy.sparse.csr_matrix(W) 
  return W                          

では解説しまーす。
方針は、2D行DT列の行列を1まとまりとして考えます。
最初のD行が静的特徴量、次のD行が動的特徴量に関連します。

w0 = scipy.sparse.lil_matrix((D, D*T))

これは、D行DT列のゼロ行列w0を生成します。時刻tにおける静的特徴量に関連する行ブロックです。
次のw1は動的特徴量の算出を担当します。

例えば音声だとDはメルケプストラムなどで39次元とかありますし、Tは発話単位のフレーム数で1000次元くらいあるので素直にnumpy.arrayを使うと1521000個の要素を持ちます。大変な事ですね。

なのでscipyのスパース行列を利用します。
- どのインデックスに0以外の値を持つか
- そこにどの値が入ってるか
という情報だけを格納します。なので今回みたいな0が大半を占める行列を扱う時にはもってこいなわけです。

w0[0:, t*D:(t+1)*D] = scipy.sparse.diags(np.ones(D), 0)

さっき乗せた図の各マスはD行D列の対角行列で、対角要素はマス目の数字です。
まず、「1」が入ってるところに注目してください。
時刻tにおけるブロック(2D行TD列)の上段で、t*D:(t+1)*D列に「1」が割り当てられてます。
つまり、そこのマス(D行D列のサブ行列)が単位行列になります。
上記のコードはそれを意味してます。

    if t >= 1:
      tmp1 = np.zeros(D)
      tmp1.fill(-0.5)
      w1[0:, (t - 1) * D:t * D] = scipy.sparse.diags(tmp1, 0)

今度は「-0.5」に注目します。
まぁやりたいことは、「1」のときと同じなので、さっきの図を見ながら呼んでくれればわかるかと思います。

    if t < T-1:
      tmp1 = np.zeros(D)
      tmp1.fill(0.5)
      w1[0:, (t + 1) * D:(t + 2) * D] = scipy.sparse.diags(tmp1, 0)

今度は「0.5」です。詳細は今までと同じ感じです。

次、

 W_t = scipy.sparse.vstack([w0, w1])

     if t == 0:
       W = W_t
     else:
       W = scipy.sparse.vstack([W, W_t])

  W = scipy.sparse.csr_matrix(W) 

w0とv1を垂直結合します。最初に「2D行DT列を1まとまりとして」と言いました。
これで1まとまりのブロックの完成です。

あとはそれをT回繰り返してがっちゃんこすれば
2DT行DT列の行列$\boldsymbol{W}$の完成です。やったー!!!

「R」算出の実装しまーす

def covar_matrix(Y, T, D):
  Y_mat = Y.reshape(T, int(len(Y)/T))
  S_vec = np.var(Y_mat, axis=0)

  M = len(S_vec)
  diagonal_t = 1.0/S_vec
  S_inv = np.zeros((T, M, M))
  for t in range(T):
    S_inv[t] = np.diag(diagonal_t)

  S_inv = scipy.sparse.block_diag(S_inv, format='csr')
  return S_inv

まず$\Sigma^{-1}$を求めます。共分散行列の逆行列です。ただし簡易化するために今回は対角行列化したただの分散行列にします。
対角行列の逆行列は、対角成分の逆数をとれば良いだけなのでめっちゃ簡単です。

次、

def computeR(W, S_inv):
  R1 = W.T.dot(S_inv).dot(W)
  R1 = scipy.sparse.linalg.inv(R1)
  R2 = W.T.dot(S_inv)
  R = R1.dot(R2)

  return R
\boldsymbol{R} = (\boldsymbol{W}^T\Sigma^{-1}\boldsymbol{W})^{-1}\boldsymbol{W}^T\boldsymbol{\Sigma}^{-1}

を計算してます。

全体コード

import scipy.sparse
import numpy as np

def computeW(T, D):
  for t in range(T):
    w0 = scipy.sparse.lil_matrix((D, D * T))
    w1 = scipy.sparse.lil_matrix((D, D * T)) 
    w0[0:, t * D:(t + 1) * D] = scipy.sparse.diags(np.ones(D), 0)

    if t >= 1:
      tmp1 = np.zeros(D)
      tmp1.fill(-0.5)
      w1[0:, (t - 1) * D:t * D] = scipy.sparse.diags(tmp1, 0)

    if t < T-1:
      tmp1 = np.zeros(D)
      tmp1.fill(0.5)
      w1[0:, (t + 1) * D:(t + 2) * D] = scipy.sparse.diags(tmp1, 0)


     W_t = scipy.sparse.vstack([w0, w1])

     if t == 0:
       W = W_t
     else:
       W = scipy.sparse.vstack([W, W_t])

  W = scipy.sparse.csr_matrix(W) 
  return W   


def covar_matrix(Y, T, D):
  Y_mat = Y.reshape(T, int(len(Y)/T))
  S_vec = np.var(Y_mat, axis=0)

  M = len(S_vec)
  diagonal_t = 1.0/S_vec
  S_inv = np.zeros((T, M, M))
  for t in range(T):
    S_inv[t] = np.diag(diagonal_t)

  S_inv = scipy.sparse.block_diag(S_inv, format='csr')
  return S_inv             


def computeR(W, S_inv):
  R1 = W.T.dot(S_inv).dot(W)
  R1 = scipy.sparse.linalg.inv(R1)
  R2 = W.T.dot(S_inv)
  R = R1.dot(R2)

  return R


def super2static(Y, W, T, D):
  S_inv = covar_matrix(Y, T, D)
  R = limitationR(W, S_inv)
  return R.dot(Y)     


def static2super(y, T, D):
  y_ = y.reshape(T*D, 1)
  W = computeW(T, D)
  Y = W.dot(y_)
  return Y, W

DeepLearningに組み込む

よく見るのはMSE基準による最適化です。

  L = \frac{1}{T}(\boldsymbol{y} - \hat{\boldsymbol{y}})^T(\boldsymbol{y} - \hat{\boldsymbol{y}})

単に教師データ$\boldsymbol{y}$と生成データ$\hat{\boldsymbol{y}}$の誤差を
フレーム方向に(データ数方向に)平均してます。

ただこのとき、$\boldsymbol{y}$はある関数$f$を介して入力$\boldsymbol{x}$から生成されたデータとします。

$\boldsymbol{x}$はミニバッチ学習などを採用していれば、無造作に選ばれたN個のデータからなるので
n番目のデータとn+1番目のデータに繋がりはありません。

MGE基準の最適化

こいつはMinimum-Generation-Errorのことで、

\begin{align}
  L &= \frac{1}{T}(\boldsymbol{RY} - \hat{\boldsymbol{y}})^T(\boldsymbol{RY} - \hat{\boldsymbol{y}}) \\
  \boldsymbol{Y} &= f(\boldsymbol{x})
\end{align}

のように、静的・動的特徴量を出力するモデルに対して、本来欲しい静的特徴量間の誤差を用いて最適化します。
※もし音声変換ならモデルの入力は$\boldsymbol{X}$など

なので、この状況下では
- ミニバッチ単位では無く、1塊のデータ単位での学習(例えば1発話など)
 ※その都度$\boldsymbol{Y}$から制約行列$\boldsymbol{R}$を推定
になります。
これが「Generation」の由来でしょうかね。

今回はscipyのスパース行列でMLPGを実装しましたが、DeepLearningをPyTorchで実装するならMLPGもそれに対応させる必要があります。
それは疲れたのでまたいつかやります。

まとめ

ということで今回はMLPGとMGE基準についてメモを書きました。
細かいミスはごめんなさい。
勘違いや誤りがあれば教えていただけると幸いです。。。

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

Python,AI関連の英語語源の調査

概要

(特に分野を限定するものではないですが、)英語がなんとなくしっくりこない場合に語源を調べたりすると思う。
いま現在であれば、以下のサイトの語源が詳しいことがよく知られている。

image.png

etymonline.com を使う場合の注意事項

以下の注意事項がある(逐次追加)

  • 予備知識が必要。主に、ヨーロッパの歴史を少し知らないと、なんのことかわからない場合がある。

予備知識

ヨーロッパの時代区分

(↓ Wikipediaを参考にしたと思う。)

時代 境界
古代
西ローマ帝国の滅亡
中世
東ローマ帝国の滅亡、ルネサンス、大航海時代、宗教改革
近世
市民革命(特にフランス革命)、産業革命
近代

ここでポイントは、以下。

  • 中世の一部は、「暗黒時代」とも呼ばれており、あまり、どんどん文明とかが発展するようないい時代ではなかったらしい。民族の移動とかの影響のためか?。このあたりが日本とかとは、すこし、ノリの違うところか。(←歴史の話なので、違う意見や、見解の見直しなど、いろいろあるが。。。。)

以下は、中世の様子。えって、感じ。
Wikipedia(https://en.wikipedia.org/wiki/Middle_Ages)より引用。

image.png

語源調査結果

(20回に1回ぐらい、調べて良かったと思うことあり。そのあたりのよかった単語を、紹介する。後報)

まとめ

特にありません。

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

PyTorchのチュートリアルA 60 MINUTE BLITZをやってみた

PyTorchのチュートリアルA 60 MINUTE BLITZをやってみました
https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html

1. インストール

公式ページに行くと環境に合わせたインストールコマンドを教えてもらえます
https://pytorch.org/get-started/locally/
image.png
上記ではPython版Stable versionをWindows + CUDA10.1環境にpipでインストールする場合のコマンドが表示されています。公式ではcondaでのインストールを推奨しているようですがpipでインストールに失敗したことがないのでpipでも大丈夫だと思います。なお、python2系は対応してないのでpython3系環境にインストールしましょう。

正常にインストールされていれば以下を実行出来る筈です

python
>>> import torch
>>> x = torch.rand(5, 3)
>>> print(x)

tensor([[0.4095, 0.9509, 0.5004],
        [0.7022, 0.7838, 0.8452],
        [0.6560, 0.2921, 0.4012],
        [0.1274, 0.6157, 0.4293],
        [0.2008, 0.7606, 0.0061]])

CUDAが有効になっているか確認しておきます

python
>>> import torch
>>> torch.cuda.is_available()
True

問題ないようです

2. tensorの扱い方

np.array的な感じで使えるようになっています
https://pytorch.org/tutorials/beginner/blitz/tensor_tutorial.html#sphx-glr-beginner-blitz-tensor-tutorial-py

2-1. torch.tensorを作る

ゼロ埋め、1埋めのtensorを作る

python
>>> torch.ones(2, 2)
tensor([[1., 1.],
        [1., 1.]])

>>> torch.zeros(2, 2)
tensor([[0., 0.],
        [0., 0.]])

乱数でtensorを作る

python
>>> torch.rand(2, 2, dtype=torch.float)
tensor([[0.0020, 0.1918],
        [0.4320, 0.5111]])

listからtensorを作る

python
>>> import torch
>>> import numpy as np

>>> data = [[1, 2],[3, 4]]
>>> x_data = torch.tensor(data)
>>> x_data

tensor([[1, 2],
        [3, 4]])

np.ndarrayからtensorを作る

python
>>> arr = np.array([[1, 2],[3, 4]])
>>> x_data = torch.tensor(arr)
>>> x_data

tensor([[1, 2],
        [3, 4]], dtype=torch.int32)
python
>>> x_data = torch.from_numpy(arr)
>>> x_data

tensor([[1, 2],
        [3, 4]], dtype=torch.int32)

ちなみにtorch.from_numpy()で作った場合には元のnumpy.ndarrayに何やら仕込んでくれるようで、元になったnumpy.ndarrayオブジェクトの計算結果を作ったtensorに反映することが出来ます。

python
>>> n = np.ones(5)
>>> t = torch.from_numpy(n)
>>> t2 = torch.tensor(n)

>>> print(f"t: {t}")
>>> print(f"n: {t2}")
>>> print(f"n: {n}", '\n')

t: tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
n: tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
n: [1. 1. 1. 1. 1.] 

>>> np.add(n, 1, out=n)
>>> print(f"t: {t}")
>>> print(f"n: {t2}")
>>> print(f"n: {n}")

t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
n: tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
n: [2. 2. 2. 2. 2.]

tensorからnp.ndarrayを作る

numpy.arrayを作りたい場合は以下で作れます

python
arr = np.array(x_data)
arr

numpy()メソッドを使ってnumpy.arrayを作ることもできます

python
arr = x_data.numpy()
arr

こちらもnumpy()メソッドで作ったnumpy.ndarrayっぽいオブジェクトには何やら仕込まれているようで、元になったオブジェクトへの変更を反映することが出来ます。

python
>>> t = torch.ones(5)
>>> n = t.numpy()
>>> arr = np.array(t)

>>> print(f"t: {t}")
>>> print(f"n: {n}")
>>> print(f"arr: {arr}", '\n')

t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]
arr: [1. 1. 1. 1. 1.] 

>>> t.add_(1)
>>> print(f"t: {t}")
>>> print(f"n: {n}")
>>> print(f"arr: {arr}")

t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]
arr: [1. 1. 1. 1. 1.]

2-2. tensorをcudaで扱えるようにする

PyTorchは同じ環境でCPUとGPUの両方が使えるようになっていて、どちらを使うのかをtensorやmodelのモード切り替えで明確にする仕組みのようです。

GPUで扱う

to('cuda')で切り替える際にCUDAデバイスへの割り当てが行われて、どのCUDAデバイスに割り当てられているかが表示されるようになります

python
>>> tensor = torch.tensor([[1, 2], [1, 2]])
>>> if torch.cuda.is_available():
>>>     tensor = tensor.to('cuda')
>>> tensor
tensor([[1, 2],
        [1, 2]], device='cuda:0')

to('cuda')の代わりにcuda()でも同じです

python
>>> tensor = tensor.cuda()
>>> tensor
tensor([[1, 2],
        [1, 2]], device='cuda:0')

CPUで扱う

CPUで扱うように戻すことも出来ます

python
>>> tensor = tensor.to('cpu')
>>> tensor
tensor([[1, 2],
        [1, 2]])
python
>>> tensor = tensor.cuda()
>>> tensor
tensor([[1, 2],
        [1, 2]])

2-3. tensorの一部を書き換える

python
>>> tensor = torch.ones(4, 4)
>>> tensor[:, 1] = 0
>>> tensor[3, 2] = 4
>>> print(tensor)

tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 4., 1.]])

2-4. tensorの計算

使えるメソッドは以下にまとまっています
https://pytorch.org/docs/stable/torch.html

スカラー和

python
>>> arr = np.arange(6).reshape(2, 3)
>>> tensor = torch.tensor(arr)
>>> print(tensor, '\n')

tensor([[0, 1, 2],
        [3, 4, 5]], dtype=torch.int32) 

>>> print(tensor + 5, '\n')
>>> print(tensor, '\n')

tensor([[ 5,  6,  7],
        [ 8,  9, 10]], dtype=torch.int32) 

tensor([[0, 1, 2],
        [3, 4, 5]], dtype=torch.int32) 

>>> tensor = tensor + 5
>>> print(tensor)

tensor([[ 5,  6,  7],
        [ 8,  9, 10]], dtype=torch.int32)

要素ごとの積

python
>>> tensor = torch.ones(4, 4)
>>> tensor[:, 1] = 0
>>> print(tensor, '\n')

tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]]) 

>>> # This computes the element-wise product
>>> print(f"tensor.mul(tensor) \n {tensor.mul(tensor)} \n")
>>> # Alternative syntax:
>>> print(f"tensor * tensor \n {tensor * tensor}")

tensor.mul(tensor) 
 tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]]) 

tensor * tensor 
 tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])

行列積

python
>>> print(f"tensor.matmul(tensor.T) \n {tensor.matmul(tensor.T)} \n")

tensor.matmul(tensor.T) 
 tensor([[3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.]]) 

>>> # Alternative syntax:
>>> print(f"tensor @ tensor.T \n {tensor @ tensor.T}")

tensor @ tensor.T 
 tensor([[3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.]])

3. autogradについて

逆誤差伝搬による微分を実現するtorch.autogradの動作を解説してくれています
https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html#sphx-glr-beginner-blitz-autograd-tutorial-py

torch.autogradのAPIリファレンス
https://pytorch.org/docs/stable/autograd.html

3-1. 勾配の算出とoptimize

学習済みresnet18モデルに乱数で作ったデータをつっこんで勾配を算出してみましょう

モデルの読み込みとデータの作成

python
import numpy as np
import torch, torchvision
import matplotlib.pyplot as plt

model = torchvision.models.resnet18(pretrained=True)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)

arr = np.array(data)
im = np.stack([arr[0, k, :, :] for k in  range(3)], axis=2)
plt.imshow(im)
plt.show()

plt.plot(labels[0])
plt.show()



乱数なので画像もlabelも当然意味をなさない値が入っていますがとりあえずやってみるだけなので気にしないで進めます

予測を実行

modelにデータを放り込めば推定してくれます

python
prediction = model(data)  # forward pass
print(prediction.shape)

plt.plot(prediction[0].detach(), label='prediction')
plt.plot(labels.numpy()[0], label='labels')
plt.legend()
plt.show()


乱数で作った画像を放り込まれたモデルが気の毒になります

lossを算出

lossを算出します

python
>>> loss = (prediction - labels).sum()
>>> loss.backward() # backward pass
>>> loss

tensor(-503.5731, grad_fn=<SumBackward0>)

勾配算出の為に何やら付いてますね

optimizerを作ってパラメーターを更新する

python
>>> optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
>>> optim.step() #gradient descent

これでパラメーターの更新が完了です

実際の学習では乱数ではなく何かが写ったまともな画像と正解ラベルをバッチ枚数ごとに放り込んでいって、学習データ全体がepoch回学習されるように繰り返すわけですね。この辺、実際の画像でデモした方が分かりやすい気がするんですがこの手のライブラリを作るような人にはこれで充分ということなのかも知れません。

3-2. autogradの動作

以下のような関数を微分することを考えてみます

Q = 3a^3 + b^2

a, bで偏微分すると以下のようになります

\frac{\delta Q}{\delta a} = 9a^2 \\
\frac{\delta Q}{\delta b} = 2b

これをtorch.autogradにやってもらいましょう

a, bをつくる

python
import torch
a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)

a, bの関数Qをつくる

python
>>> Q = 3 * a ** 3 - b ** 2
>>> Q
tensor([-12.,  65.], grad_fn=<SubBackward0>)

Qの偏微分を実行

先程と同様にbackward()すれば偏微分が実行されますが、torchはloss functionのようなスカラー量が求まる関数しか偏微分できませんので、Qをどう足し合わせるか指定してやる必要があります。行列を引数として渡してやれば内積して得られた値を使ってやってくれますので、以下ではそれぞれ1倍して足し合わせた値を使ってやってくれます。

python
>>> external_grad = torch.tensor([1., 1.])
>>> Q.backward(gradient=external_grad)

>>> # check if collected gradients are correct
>>> print(9*a**2 == a.grad, a.grad)
>>> print(-2*b == b.grad, b.grad)

tensor([False, False]) tensor([18.0000, 40.5000])
tensor([False, False]) tensor([-6., -4.])

a, bを微分して得られる9a^2と-2bが得られているのが分かります

3-3. 微分を算出しないノードを指定する

requires_gradを省略するかrequires_grad=Falseにすると偏微分が算出されなくなります。偏微分の算出は計算コストが掛かりますので、学習する必要がないパラメーターについては偏微分を算出しないように設定するのが良いようです。

python
x = torch.rand(5, 5)
y = torch.rand(5, 5)
z = torch.rand((5, 5), requires_grad=True)

a = x + y
print(f"Does `a` require gradients? : {a.requires_grad}")
b = x + z
print(f"Does `b` require gradients?: {b.requires_grad}")

3-4. モデルの微分を算出しないように設定する

学習済みモデルを別のモデルに組み込んで学習をかけるような場合にモデルの学習を行わないようにするには以下のようにしてやります。model.parameters()で偉えるモデルのそれぞれのパラメーターにあたるtensorのrequires_gradをFalseにしてやるわけですね。

python
from torch import nn, optim

model = torchvision.models.resnet18(pretrained=True)

# Freeze all the parameters in the network
for param in model.parameters():
    param.requires_grad = False

model.fc = nn.Linear(512, 10)

# Optimize only the classifier
optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)

4. CNNをやってみる

ようやく実際のneural networkのデモに到着しました
https://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html#sphx-glr-beginner-blitz-neural-networks-tutorial-py

4-1. ネットワークをつくる

PyTorchでは以下のような書き方でネットワークをつくるようです。クラスとして扱うのはtensorflowと同様ですが、こちらの方が書き方の自由度は高そうです。

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


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # 1 input image channel, 6 output channels, 3x3 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 6 * 6, 120)  # 6*6 from image dimension
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If the size is a square you can only specify a single number
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
print(net)

畳み込み2層と全結合2層+出力1層のネットワークが構築されました

output
Net(
  (conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
  (fc1): Linear(in_features=576, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

4-2. パラメーターの確認方法

parameters()メソッドでパラメーターを取得できます。weight, biasの順で入っているので5層だと10個のパラメーターがあることになります。

python
>>> params = list(net.parameters())
>>> print(len(params))

10

parameters()はtorch.tensor形式のパラメーターをlistに入れて出してくれるので、例えば1層目のweightは以下のようにすれば見られます

python
>>> print(params[0].size(), '\n')  # conv1's .weight
>>> print(params[0])

torch.Size([6, 1, 3, 3]) 

Parameter containing:
tensor([[[[-0.2333,  0.2431,  0.1944],
          [ 0.0508,  0.0473,  0.0961],
          [ 0.2589, -0.0621, -0.3103]]],


        [[[-0.1810, -0.3264,  0.2296],
          [ 0.1090, -0.1363,  0.2106],
          [-0.1993,  0.1884,  0.2740]]],


        [[[-0.1940,  0.2546, -0.1444],
          [ 0.2365, -0.2924,  0.0217],
          [ 0.3072, -0.0517, -0.1252]]],


        [[[ 0.3191, -0.2198, -0.1129],
          [-0.1344, -0.0281,  0.3153],
          [-0.2891,  0.1994, -0.2489]]],


        [[[ 0.2201,  0.1512, -0.2975],
          [-0.2770,  0.1493,  0.0099],
          [ 0.0210,  0.3213,  0.2986]]],


        [[[-0.0330, -0.3037, -0.0267],
          [-0.2693, -0.1042, -0.0520],
          [-0.0537, -0.2117, -0.0070]]]], requires_grad=True)

4-3. 推定する

乱数で入力データを作ってネットワークに通してみます。ネットワーク自体も初期値として入力した乱数がパラメーターとして入っているだけの意味をなさないものなので、まあなんか出てきましたというだけの出力になりますが、とりあえずのデモという事ですね。

python
>>> input = torch.randn(1, 1, 32, 32)
>>> out = net(input)
>>> print(out.shape, '\n')
>>> print(out)

torch.Size([1, 10])
tensor([[-0.0469,  0.0536,  0.0633, -0.0827, -0.0329,  0.0237, -0.0360,  0.0352,
         -0.0768, -0.1269]], grad_fn=<AddmmBackward>)

inputってpythonの標準関数に同じ名前のものがあるので使わない方が良い気がするんですが、まあチュートリアルなので気にせずいきましょう。

4-4. lossを算出する

以下ではloss functionとしてmseを使ってlossを算出しています

python
output = net(input)
target = torch.randn(10)  # a dummy target, for example
target = target.view(1, -1)  # make it the same shape as output
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)

lossを定義するときに勾配算出の為の関数も登録されます

python
>>> print(loss.grad_fn)  # MSELoss
>>> print(loss.grad_fn.next_functions[0][0])  # Linear
>>> print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU

<MseLossBackward object at 0x0000020A362DF908>
<AddmmBackward object at 0x0000020A362DF708>
<AccumulateGrad object at 0x0000020A362DF908>

4-5. 勾配の算出

autogradは前回の勾配を覚えておいてbackward()したときに今回の勾配と足し合わせて返してくれますが、CNNだとその必要はないのでzero_grad()により前回の勾配を消してから微分します

python
>>> net.zero_grad()     # zeroes the gradient buffers of all parameters
>>> print('conv1.bias.grad before backward')
>>> print(net.conv1.bias.grad)

conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])

>>> loss.backward()
>>> print('conv1.bias.grad after backward')
>>> print(net.conv1.bias.grad)

conv1.bias.grad after backward
tensor([ 0.0004,  0.0189,  0.0209, -0.0024,  0.0032,  0.0082])

4-6. 学習の反映

例えばSGDなら以下のようにすれば更新できます

python
learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)

一般的なoptimizerはクラスを用意してくれていますのでそれを使えば良いようです
https://pytorch.org/docs/stable/optim.html

optimizerの定義からlossの算出、パラメーターの更新までを通してやると以下のようになります

python
import torch.optim as optim

# create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)

# in your training loop:
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update

5. まとめ

PyTorchの挙動を強く意識させる内容になっていて、tensorflowなどで既にneural networkに慣れている人向けのチュートリアルかもしれません。おかげでautogradの挙動への理解は結構進みましたが初めて触る人がここから入るとちょっと分かりにくいかも。

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

PyTorchのチュートリアルをやってみた

PyTorchのチュートリアルA 60 MINUTE BLITZをやってみました
https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html

1. インストール

公式ページに行くと環境に合わせたインストールコマンドを教えてもらえます
https://pytorch.org/get-started/locally/
image.png
上記ではPython版Stable versionをWindows + CUDA10.1環境にpipでインストールする場合のコマンドが表示されています。公式ではcondaでのインストールを推奨しているようですがpipでインストールに失敗したことがないのでpipでも大丈夫だと思います。なお、python2系は対応してないのでpython3系環境にインストールしましょう。

正常にインストールされていれば以下を実行出来る筈です

python
>>> import torch
>>> x = torch.rand(5, 3)
>>> print(x)

tensor([[0.4095, 0.9509, 0.5004],
        [0.7022, 0.7838, 0.8452],
        [0.6560, 0.2921, 0.4012],
        [0.1274, 0.6157, 0.4293],
        [0.2008, 0.7606, 0.0061]])

CUDAが有効になっているか確認しておきます

python
>>> import torch
>>> torch.cuda.is_available()
True

問題ないようです

2. tensorの扱い方

np.array的な感じで使えるようになっています
https://pytorch.org/tutorials/beginner/blitz/tensor_tutorial.html#sphx-glr-beginner-blitz-tensor-tutorial-py

2-1. torch.tensorを作る

ゼロ埋め、1埋めのtensorを作る

python
>>> torch.ones(2, 2)
tensor([[1., 1.],
        [1., 1.]])

>>> torch.zeros(2, 2)
tensor([[0., 0.],
        [0., 0.]])

乱数でtensorを作る

python
>>> torch.rand(2, 2, dtype=torch.float)
tensor([[0.0020, 0.1918],
        [0.4320, 0.5111]])

listからtensorを作る

python
>>> import torch
>>> import numpy as np

>>> data = [[1, 2],[3, 4]]
>>> x_data = torch.tensor(data)
>>> x_data

tensor([[1, 2],
        [3, 4]])

np.ndarrayからtensorを作る

python
>>> arr = np.array([[1, 2],[3, 4]])
>>> x_data = torch.tensor(arr)
>>> x_data

tensor([[1, 2],
        [3, 4]], dtype=torch.int32)
python
>>> x_data = torch.from_numpy(arr)
>>> x_data

tensor([[1, 2],
        [3, 4]], dtype=torch.int32)

ちなみにtorch.from_numpy()で作った場合には元のnumpy.ndarrayに何やら仕込んでくれるようで、元になったnumpy.ndarrayオブジェクトの計算結果を作ったtensorに反映することが出来ます。

python
>>> n = np.ones(5)
>>> t = torch.from_numpy(n)
>>> t2 = torch.tensor(n)

>>> print(f"t: {t}")
>>> print(f"n: {t2}")
>>> print(f"n: {n}", '\n')

t: tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
n: tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
n: [1. 1. 1. 1. 1.] 

>>> np.add(n, 1, out=n)
>>> print(f"t: {t}")
>>> print(f"n: {t2}")
>>> print(f"n: {n}")

t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
n: tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
n: [2. 2. 2. 2. 2.]

tensorからnp.ndarrayを作る

numpy.arrayを作りたい場合は以下で作れます

python
arr = np.array(x_data)
arr

numpy()メソッドを使ってnumpy.arrayを作ることもできます

python
arr = x_data.numpy()
arr

こちらもnumpy()メソッドで作ったnumpy.ndarrayっぽいオブジェクトには何やら仕込まれているようで、元になったオブジェクトへの変更を反映することが出来ます。

python
>>> t = torch.ones(5)
>>> n = t.numpy()
>>> arr = np.array(t)

>>> print(f"t: {t}")
>>> print(f"n: {n}")
>>> print(f"arr: {arr}", '\n')

t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]
arr: [1. 1. 1. 1. 1.] 

>>> t.add_(1)
>>> print(f"t: {t}")
>>> print(f"n: {n}")
>>> print(f"arr: {arr}")

t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]
arr: [1. 1. 1. 1. 1.]

2-2. tensorをcudaで扱えるようにする

PyTorchは同じ環境でCPUとGPUの両方が使えるようになっていて、どちらを使うのかをtensorやmodelのモード切り替えで明確にする仕組みのようです。

GPUで扱う

to('cuda')で切り替える際にCUDAデバイスへの割り当てが行われて、どのCUDAデバイスに割り当てられているかが表示されるようになります

python
>>> tensor = torch.tensor([[1, 2], [1, 2]])
>>> if torch.cuda.is_available():
>>>     tensor = tensor.to('cuda')
>>> tensor
tensor([[1, 2],
        [1, 2]], device='cuda:0')

to('cuda')の代わりにcuda()でも同じです

python
>>> tensor = tensor.cuda()
>>> tensor
tensor([[1, 2],
        [1, 2]], device='cuda:0')

CPUで扱う

CPUで扱うように戻すことも出来ます

python
>>> tensor = tensor.to('cpu')
>>> tensor
tensor([[1, 2],
        [1, 2]])
python
>>> tensor = tensor.cuda()
>>> tensor
tensor([[1, 2],
        [1, 2]])

2-3. tensorの一部を書き換える

python
>>> tensor = torch.ones(4, 4)
>>> tensor[:, 1] = 0
>>> tensor[3, 2] = 4
>>> print(tensor)

tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 4., 1.]])

2-4. tensorの計算

使えるメソッドは以下にまとまっています
https://pytorch.org/docs/stable/torch.html

スカラー和

python
>>> arr = np.arange(6).reshape(2, 3)
>>> tensor = torch.tensor(arr)
>>> print(tensor, '\n')

tensor([[0, 1, 2],
        [3, 4, 5]], dtype=torch.int32) 

>>> print(tensor + 5, '\n')
>>> print(tensor, '\n')

tensor([[ 5,  6,  7],
        [ 8,  9, 10]], dtype=torch.int32) 

tensor([[0, 1, 2],
        [3, 4, 5]], dtype=torch.int32) 

>>> tensor = tensor + 5
>>> print(tensor)

tensor([[ 5,  6,  7],
        [ 8,  9, 10]], dtype=torch.int32)

要素ごとの積

python
>>> tensor = torch.ones(4, 4)
>>> tensor[:, 1] = 0
>>> print(tensor, '\n')

tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]]) 

>>> # This computes the element-wise product
>>> print(f"tensor.mul(tensor) \n {tensor.mul(tensor)} \n")
>>> # Alternative syntax:
>>> print(f"tensor * tensor \n {tensor * tensor}")

tensor.mul(tensor) 
 tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]]) 

tensor * tensor 
 tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])

行列積

python
>>> print(f"tensor.matmul(tensor.T) \n {tensor.matmul(tensor.T)} \n")

tensor.matmul(tensor.T) 
 tensor([[3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.]]) 

>>> # Alternative syntax:
>>> print(f"tensor @ tensor.T \n {tensor @ tensor.T}")

tensor @ tensor.T 
 tensor([[3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.]])

3. autogradについて

逆誤差伝搬による微分を実現するtorch.autogradの動作を解説してくれています
https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html#sphx-glr-beginner-blitz-autograd-tutorial-py

torch.autogradのAPIリファレンス
https://pytorch.org/docs/stable/autograd.html

3-1. 勾配の算出とoptimize

学習済みresnet18モデルに乱数で作ったデータをつっこんで勾配を算出してみましょう

モデルの読み込みとデータの作成

python
import numpy as np
import torch, torchvision
import matplotlib.pyplot as plt

model = torchvision.models.resnet18(pretrained=True)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)

arr = np.array(data)
im = np.stack([arr[0, k, :, :] for k in  range(3)], axis=2)
plt.imshow(im)
plt.show()

plt.plot(labels[0])
plt.show()



乱数なので画像もlabelも当然意味をなさない値が入っていますがとりあえずやってみるだけなので気にしないで進めます

予測を実行

modelにデータを放り込めば推定してくれます

python
prediction = model(data)  # forward pass
print(prediction.shape)

plt.plot(prediction[0].detach(), label='prediction')
plt.plot(labels.numpy()[0], label='labels')
plt.legend()
plt.show()


乱数で作った画像を放り込まれたモデルが気の毒になります

lossを算出

lossを算出します

python
>>> loss = (prediction - labels).sum()
>>> loss.backward() # backward pass
>>> loss

tensor(-503.5731, grad_fn=<SumBackward0>)

勾配算出の為に何やら付いてますね

optimizerを作ってパラメーターを更新する

python
>>> optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
>>> optim.step() #gradient descent

これでパラメーターの更新が完了です

実際の学習では乱数ではなく何かが写ったまともな画像と正解ラベルをバッチ枚数ごとに放り込んでいって、学習データ全体がepoch回学習されるように繰り返すわけですね。この辺、実際の画像でデモした方が分かりやすい気がするんですがこの手のライブラリを作るような人にはこれで充分ということなのかも知れません。

3-2. autogradの動作

以下のような関数を微分することを考えてみます

Q = 3a^3 + b^2

a, bで偏微分すると以下のようになります

\frac{\delta Q}{\delta a} = 9a^2 \\
\frac{\delta Q}{\delta b} = 2b

これをtorch.autogradにやってもらいましょう

a, bをつくる

python
import torch
a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)

a, bの関数Qをつくる

python
>>> Q = 3 * a ** 3 - b ** 2
>>> Q
tensor([-12.,  65.], grad_fn=<SubBackward0>)

Qの偏微分を実行

先程と同様にbackward()すれば偏微分が実行されますが、torchはloss functionのようなスカラー量が求まる関数しか偏微分できませんので、Qをどう足し合わせるか指定してやる必要があります。行列を引数として渡してやれば内積して得られた値を使ってやってくれますので、以下ではそれぞれ1倍して足し合わせた値を使ってやってくれます。

python
>>> external_grad = torch.tensor([1., 1.])
>>> Q.backward(gradient=external_grad)

>>> # check if collected gradients are correct
>>> print(9*a**2 == a.grad, a.grad)
>>> print(-2*b == b.grad, b.grad)

tensor([False, False]) tensor([18.0000, 40.5000])
tensor([False, False]) tensor([-6., -4.])

a, bを微分して得られる9a^2と-2bが得られているのが分かります

3-3. 微分を算出しないノードを指定する

requires_gradを省略するかrequires_grad=Falseにすると偏微分が算出されなくなります。偏微分の算出は計算コストが掛かりますので、学習する必要がないパラメーターについては偏微分を算出しないように設定するのが良いようです。

python
x = torch.rand(5, 5)
y = torch.rand(5, 5)
z = torch.rand((5, 5), requires_grad=True)

a = x + y
print(f"Does `a` require gradients? : {a.requires_grad}")
b = x + z
print(f"Does `b` require gradients?: {b.requires_grad}")

3-4. モデルの微分を算出しないように設定する

学習済みモデルを別のモデルに組み込んで学習をかけるような場合にモデルの学習を行わないようにするには以下のようにしてやります。model.parameters()で偉えるモデルのそれぞれのパラメーターにあたるtensorのrequires_gradをFalseにしてやるわけですね。

python
from torch import nn, optim

model = torchvision.models.resnet18(pretrained=True)

# Freeze all the parameters in the network
for param in model.parameters():
    param.requires_grad = False

model.fc = nn.Linear(512, 10)

# Optimize only the classifier
optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)

4. CNNをやってみる

ようやく実際のneural networkのデモに到着しました
https://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html#sphx-glr-beginner-blitz-neural-networks-tutorial-py

4-1. ネットワークをつくる

PyTorchでは以下のような書き方でネットワークをつくるようです。クラスとして扱うのはtensorflowと同様ですが、こちらの方が書き方の自由度は高そうです。

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


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # 1 input image channel, 6 output channels, 3x3 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 6 * 6, 120)  # 6*6 from image dimension
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If the size is a square you can only specify a single number
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
print(net)

畳み込み2層と全結合2層+出力1層のネットワークが構築されました

output
Net(
  (conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
  (fc1): Linear(in_features=576, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

4-2. パラメーターの確認方法

parameters()メソッドでパラメーターを取得できます。weight, biasの順で入っているので5層だと10個のパラメーターがあることになります。

python
>>> params = list(net.parameters())
>>> print(len(params))

10

parameters()はtorch.tensor形式のパラメーターをlistに入れて出してくれるので、例えば1層目のweightは以下のようにすれば見られます

python
>>> print(params[0].size(), '\n')  # conv1's .weight
>>> print(params[0])

torch.Size([6, 1, 3, 3]) 

Parameter containing:
tensor([[[[-0.2333,  0.2431,  0.1944],
          [ 0.0508,  0.0473,  0.0961],
          [ 0.2589, -0.0621, -0.3103]]],


        [[[-0.1810, -0.3264,  0.2296],
          [ 0.1090, -0.1363,  0.2106],
          [-0.1993,  0.1884,  0.2740]]],


        [[[-0.1940,  0.2546, -0.1444],
          [ 0.2365, -0.2924,  0.0217],
          [ 0.3072, -0.0517, -0.1252]]],


        [[[ 0.3191, -0.2198, -0.1129],
          [-0.1344, -0.0281,  0.3153],
          [-0.2891,  0.1994, -0.2489]]],


        [[[ 0.2201,  0.1512, -0.2975],
          [-0.2770,  0.1493,  0.0099],
          [ 0.0210,  0.3213,  0.2986]]],


        [[[-0.0330, -0.3037, -0.0267],
          [-0.2693, -0.1042, -0.0520],
          [-0.0537, -0.2117, -0.0070]]]], requires_grad=True)

4-3. 推定する

乱数で入力データを作ってネットワークに通してみます。ネットワーク自体も初期値として入力した乱数がパラメーターとして入っているだけの意味をなさないものなので、まあなんか出てきましたというだけの出力になりますが、とりあえずのデモという事ですね。

python
>>> input = torch.randn(1, 1, 32, 32)
>>> out = net(input)
>>> print(out.shape, '\n')
>>> print(out)

torch.Size([1, 10])
tensor([[-0.0469,  0.0536,  0.0633, -0.0827, -0.0329,  0.0237, -0.0360,  0.0352,
         -0.0768, -0.1269]], grad_fn=<AddmmBackward>)

inputってpythonの標準関数に同じ名前のものがあるので使わない方が良い気がするんですが、まあチュートリアルなので気にせずいきましょう。

4-4. lossを算出する

以下ではloss functionとしてmseを使ってlossを算出しています

python
output = net(input)
target = torch.randn(10)  # a dummy target, for example
target = target.view(1, -1)  # make it the same shape as output
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)

lossを定義するときに勾配算出の為の関数も登録されます

python
>>> print(loss.grad_fn)  # MSELoss
>>> print(loss.grad_fn.next_functions[0][0])  # Linear
>>> print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU

<MseLossBackward object at 0x0000020A362DF908>
<AddmmBackward object at 0x0000020A362DF708>
<AccumulateGrad object at 0x0000020A362DF908>

4-5. 勾配の算出

autogradは前回の勾配を覚えておいてbackward()したときに今回の勾配と足し合わせて返してくれますが、CNNだとその必要はないのでzero_grad()により前回の勾配を消してから微分します

python
>>> net.zero_grad()     # zeroes the gradient buffers of all parameters
>>> print('conv1.bias.grad before backward')
>>> print(net.conv1.bias.grad)

conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])

>>> loss.backward()
>>> print('conv1.bias.grad after backward')
>>> print(net.conv1.bias.grad)

conv1.bias.grad after backward
tensor([ 0.0004,  0.0189,  0.0209, -0.0024,  0.0032,  0.0082])

4-6. 学習の反映

例えばSGDなら以下のようにすれば更新できます

python
learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)

一般的なoptimizerはクラスを用意してくれていますのでそれを使えば良いようです
https://pytorch.org/docs/stable/optim.html

optimizerの定義からlossの算出、パラメーターの更新までを通してやると以下のようになります

python
import torch.optim as optim

# create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)

# in your training loop:
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update

5. まとめ

PyTorchの挙動を強く意識させる内容になっていて、tensorflowなどで既にneural networkに慣れている人向けのチュートリアルかもしれません。おかげでautogradの挙動への理解は結構進みましたが初めて触る人がここから入るとちょっと分かりにくいかも。

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

連続した数値を1桁区切りで配列にする

num = 12345
num_arr = [int(n) for n in list(str(num))]

print(num_arr) #=> [1, 2, 3, 4, 5]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python] 既存のPowerpointのテキストをフォーマットを変えずに変更する python-pptx

python-pptxでPowerPointのレポートを操作したい時、事前にテンプレートを用意しておいて文字のフォーマットはそのままで内容の一部を変更したいことがあります。思ったよりも面倒でしたので、こちらの備忘録として残します。

動作を確認するために、以下のようにフォーマットされた文字列のあるPPTファイルの一部(「。。。」)を今日の日付(2021/3/6)で変換するコードを書いてみました。元の文字は影をつけたり、下線をつけたりと、いろいろと装飾してあります。

スクリーンショット 2021-03-06 122130.png

なお、既存の文字を置き換える方法は、python-pptxの作者であるScannyさんがこちらで説明していますので、その関数(モジュール)はそのまま利用します。
https://github.com/scanny/python-pptx/issues/285
replace_paragraph_text_retaining_initial_formattingという名前の長い関数がそうです。

私が工夫したポイントとしては、元のファイルの中から該当するparagraphオブジェクトを探すところです。このオブジェクトの関係がいろいろとややこしいのですが、こちらを参考にさせていただきました。
https://www.relief.jp/docs/python-pptx-font-color.html

import pptx, re
from pptx.util import Cm, Pt, Inches

def replace_paragraph_text_retaining_initial_formatting(paragraph, new_text):
    p = paragraph._p # the lxml element containing the <a:p> paragraph element
    # remove all but the first run
    for idx, run in enumerate(paragraph.runs):
        if idx == 0:
            continue
        p.remove(run._r)
        paragraph.runs[0].text = new_text

prs = pptx.Presentation("元のファイル.pptx")
slide=prs.slides[0] #先頭のページを選択

for shp in slide.shapes:

    if shp.has_text_frame:
        if "日付は" in shp.text: #変更する文字列の一部でマッチさせる
            new_text=re.sub('。。。', '2021年3月6日', shp.text) #置き換え
            replace_paragraph_text_retaining_initial_formatting(shp.text_frame.paragraphs[0], new_text)

prs.save('変更後のファイル.pptx')

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

MacでSelenum+PyautoGUIを使ってZoomに自動出席

自動でZoom出席する

半年ほど前にSeleniumでZoomに自動で出席できないかトライしてみた(オンライン授業にPython+Seleniumで自動出席したい!)のですが、Seleniumのみではシステムダイアログをクリック出来ず、Zoom会議に参加することはできませんでした。

その後、@hima_zin331さんの「PythonでZoomミーティングに自動入室しよう」という記事を拝見しました。
この記事ではWindowsにてSelenium+Pywinautoを用いてZoomにログインし、指定時間画面録画後に自動でZoomを退出しています。

当時の私はSeleniumで遊ぶことが目的だったのでSeleniumのみで出席することを考えていたのですが、本記事ではSelenium以外のライブラリを利用して、MacでもZoomに自動出席ができないかを検討します。
なお、本プログラムはあくまで知的好奇心を満たす為のものです。本プログラムを利用して発生したあらゆる損失や不利益について当方は一切の責任を負いません

環境

macOS Big Sur 11.2.2
Python 3.8.6
PyautoGUI 0.9.52
Google Chrome 88.0.4324.192(Official Build) (x86_64)

PyautoGUIについて

今回改めて自動出席プログラムに挑戦しようと考えた最大の理由がPyautoGUIの存在です。
PyAutoGUIは、Python のモジュールの一つで、マウスやキーボード操作を自動化することが出来ます。イメージマッチングが簡単に利用でき、クロスプラットフォームなのでMac、Windows、Linuxで利用出来ます。

参考

事前準備

  • Zoomをインストール、ログインの後に以下の様に設定を変更する。
    • ミーティング接続時に、自動的にコンピュータオーディオに接続
    • ミーティングの参加時にマイクをミュートに設定
  • command+Shift+5を押して、Macの画面撮影録画バーを表示する。「画面全体を収録」か「選択部分を収録」をクリックし、録画はせずに閉じる。(macOS Mojave以降の機能です。)録音には「なし」か「内臓マイク」を選択できます。 Zoomの音声を録音したい場合はBackground Musicなどを利用します。ただし、OSbigSurは非対応の為、スナップショットバージョンを利用します。 詳細は公式ページを参照して下さい。
brew tap homebrew/cask-versions
brew install --cask background-music-pre
  • ターミナルからcrontab -eを入力して、Zoomに入室したい時間にautozoom.pyを起動するようにcronを設定します。 次の例では3月4日の21時に実行し、ログを書き出します。 ただし、/usr/sbinにPATHを通さないとPyautoGUIがエラーを吐いて実行できないので注意が必要です。
    cronでPyautoGUIを使うには何かと工夫が必要だったので、MacでのPyAutoGUIの注意点にまとめておきます。 pythonのフルパスはwhich pythonで取得出来ます。 cronが利用できない場合はtime.sleep()で軌道までの時間を無理矢理時間を潰すのもアリかもしれないです。
PATH=/usr/sbin:/usr/bin:/bin
0 21 3 4 * cd autozoom.pyのフルパス && pythonのフルパス autozoom.pyのフルパス ログ書き出したいファイルのフルパス 2>&1
  • システム環境設定から「セキュリティとプライバシー」タブをクリックして「プライバシー」を開いて下記の項目を設定し、ターミナルを再起動する。
    • アクセシビリティに「AEServer」(OSのバージョンによっては名称が異なるかも)、「/usr/sbin/cron」を追加
    • フルディスクアクセスに「ターミナル」、「/usr/sbin/cron」を追加
    • 画面収録に「ターミナル」、「/usr/sbin/cron」を追加
  • 以下のような写真を撮影する。(cmd+shift+4で任意範囲のスクリーンショットが撮影できます。)ファイル名をopenzoom.pngに変更し、後述のautozoom.pyと同じフォルダに入れておく。
    img2.png

  • 必要なライブラリのインストール(ターミナルに入力する)

#ブラウザ操作関連
pip3 install selenium
pip3 install chromedriver-binary

#PyautoGUI関連
#OSによってインストール方法が異なりますが、Macの場合は以下の様にインストール出来ます。
pip3 install pyobjc-core
pip3 install opencv_python
pip3 install pyobjc
pip3 install pyscreeze
pip3 install pyautogui

コード

autozoom.py
import datetime
import os
import pyautogui
import chromedriver_binary
from  selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('--disable-dev-shm-usage')
import time
import pyscreeze
from pyscreeze import ImageNotFoundException
pyscreeze.USE_IMAGE_NOT_FOUND_EXCEPTION = True

browser = webdriver.Chrome()
browser.implicitly_wait(3)
browser.get("入室するZoomのURL")
time.sleep(5)

nowTime = 0
maxTime = 15
p=None
while True:
    time.sleep(1)
    nowTime+=1
    print(nowTime)
    if(nowTime>maxTime):
            print(nowTime,maxTime)
            break
    try:
        p = pyautogui.locateCenterOnScreen('openzoom.png',grayscale=False,confidence=0.7)
        pyautogui.moveTo(p.x/2,p.y/2)
        if(p is not None):
            sc = pyautogui.screenshot()
            break
    except ImageNotFoundException:
        print("Image not found")

if(p is None):
    print("None and exit")
    sc = pyautogui.screenshot()
    name = 'screenshot%s.png' % (datetime.datetime.now().strftime('%Y-%m%d_%H-%M-%S-%f'))
    sc.save(name)
    exit()
else:
    pyautogui.click(p.x/2, p.y/2)

pyautogui.hotkey('command','shift','5')
time.sleep(3)
pyautogui.hotkey('enter')

time.sleep(600)

pyautogui.hotkey('command','ctrl','esc')
time.sleep(3)
pyautogui.hotkey('enter')

pyautogui.hotkey('command','q') 
time.sleep(3)
pyautogui.hotkey('enter')

コードの説明詳細

各種ライブラリ類のインポート

autozoom.py
import datetime
import os
import pyautogui
import chromedriver_binary
from  selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('--disable-dev-shm-usage')
import time
import pyscreeze
from pyscreeze import ImageNotFoundException
pyscreeze.USE_IMAGE_NOT_FOUND_EXCEPTION = True

seleniumのオプションで--disable-dev-shm-usageを指定するとメモリ不足のクラッシュを防げるらしいので利用します。
また、pyscreezeのImageNotFoundExceptionを利用することで、target.pngが見つからなかったときにImageNotFoundExceptionが発生するようにしています。

ブラウザ起動、「zoom.usを開く」をクリックしてZoomに入室

autozoom.py
browser = webdriver.Chrome()
browser.implicitly_wait(3)
browser.get("入室するZoomのURL")
time.sleep(5)

nowTime = 0
maxTime = 15
p=None
while True:
    time.sleep(1)
    nowTime+=1
    print(nowTime)
    if(nowTime>maxTime):
            print(nowTime,maxTime)
            break
    try:
        p = pyautogui.locateCenterOnScreen('openzoom.png',grayscale=False,confidence=0.7)
        if(p is not None):
            break
    except ImageNotFoundException:
        print("Image not found")

if(p is None):
    print("None and exit")
    sc = pyautogui.screenshot()
    name = 'screenshot%s.png' % (datetime.datetime.now().strftime('%Y-%m%d_%H-%M-%S-%f'))
    sc.save(name)
    exit()
else:
    pyautogui.moveTo(p.x/2,p.y/2)
    pyautogui.click(p.x/2, p.y/2)

ブラウザーを起動して、ZoomURLを開きます。1秒ごとに画面内にtarget.pngがあるかどうかを判定し、発見した場合はwhileをbreak、lauchボタンにカーソルを動かしてクリックして、Zoomに入室します。
入室後は設定通りにオーディオに自動接続し、ミュート状態になります。
PyautoGUIには一部Macで座標系が2倍になる不具合があり、p.x/2、p.y/2としていますが、座標系が正しい場合は/2は不要です。
ちなみに、カーソルを動かさずとも(pyautogui.moveTo(p.x/2,p.y/2))クリックできるが、アクセシビリティでターミナル、cron、AEServerなどが許可されていないとカーソルが動かないことを利用して、正しく動作しているか確認します。
maxTime秒(=15秒)以内に発見できなかった場合はスクリーンショットを撮影して、プログラムを終了します。撮影されたスクリーンショットがデフォルトのデスクトップの画像の場合は、画面収録にcronやターミナルが追加されていないので環境設定を確認してください。

画面録画、待機、退室処理

pyautogui.hotkey('command','shift','5')
time.sleep(3)
pyautogui.hotkey('enter')

time.sleep(600)

pyautogui.hotkey('command','ctrl','esc')
time.sleep(3)
pyautogui.hotkey('enter')

pyautogui.hotkey('command','q') 
time.sleep(3)
pyautogui.hotkey('enter')

Zoom入室後はMacのデフォルトの画面録画開始のショートカット(command+shift+5)で画面録画のツールバーを呼び出し、enterキーで画面録画を開始します。(Mac以外のOSや、Mojave以前のOSを利用する場合は画面録画ツールにショートカット設定すれば同様に利用できると思います。)
time.sleep()で録画する間待機(サンプルでは600[秒]=10[分])し、画面録画終了のショートカット(command+ctrl+esc)で録画を終了します。
最後にcommand+qで退室ボタンを表示させ、enteキーで退室します。Zoomの設定で退室の確認通知は設定でoffにすることもできます。

終わりに

PyautoGUIのイメージマッチング機能が便利すぎて感動しました。
locateCenterOnScreenの1行でイメージマッチングして座標を返すなんて便利すぎないかい、PyautoGUIくん?笑
そして、実は先日までOSがHighSierraでした。画面収録が機能がなかったので、いっちょBigSurアプデするかーにと思った結果、なんだかんだでOSをクリーンインストールしたのですが、セキュリティとプライバシーの機能変更にかなり戸惑いました。HighSierraにはフルディスクアクセスとか無かったんですよね。
思ったように動くようになってよかったです。

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

pythonで自作クラスの大小判定(ソート)を行う

やりたかったこと

pythonで簡単なクラスを定義して,そのクラスの中のある変数を基準にしてインスタンスが入っているリストのソートを行いたかったです.

どうやるんだっけ?となったのでやり方をメモしておきます.

作成したクラス

class Node:  # 木のノードを表すクラス
    def __init__(self, value: int, left = None, right = None):
        self.value: int = value  # ソートの基準としたい値
        self.left = left  # 左の子ノード
        self.right = right  # 右の子ノード

このクラスのvalueというint型の変数をもとに大小判定をしてリストをソートしたいです.

ソートするためにこのクラスのインスタンスのリストを適当に作成しておきます.

> import numpy as np
> nodes = [Node(np.random.randint(10)) for i in range(10)]
> print([node.value for node in nodes])
[6, 1, 7, 9, 8, 6, 4, 9, 6, 7]

このnodesというリストを単純にソートしてみると...

> sorted(nodes)
TypeError: '<' not supported between instances of 'Node' and 'Node'

このようにTypeErrorがでてソートすることができません.

Nodeインスタンス間の比較演算<がサポートされていないというエラーがでています.

やり方1: クラスに__lt__メソッドを定義する

class Node:  # 木のノードを表すクラス
    def __init__(self, value: int, left = None, right = None):
        self.left = left  # 左の子ノード
        self.right = right  # 右の子ノード
        self.value: int = value  # ソートの基準としたい値

    def __lt__(self, other):  # 自作クラスで大小を判別するための関数
        return self.value < other.value

上記のように__lt__メソッド(less than)を定義します.

引数にはselfotherを受け取ります.
self.valueother.valueより小さい時にはTrueをそうで無い時はFalseを返すようにします.

このように定義することでインスタンス間の大小を比較できるようになります.
(そのほかにも__eq__(equal)や__gt__(greater than)などの比較演算を定義できますが,ソートなら__lt__を定義しておけば十分でした.)

実際にソートしてみると...

> sorted_nodes = sorted(nodes)
> print([node.value for node in sorted_nodes])
[1, 4, 6, 6, 6, 7, 7, 8, 9, 9]

正しくソートされていました!

やり方2: ソート時に比較する際のキーを渡す

pythonのsorted関数ではオプション引数としてkeyを渡すことができます.
このkeyを渡すとこれを基準にソートを行なってくれます.

次のようにlambda式を使って書けます.

> sorted_nodes = sorted(nodes, key=lambda x: x.value)
> print([node.value for node in sorted_nodes])
[1, 4, 6, 6, 6, 7, 7, 8, 9, 9]

こちらも正しくソートされていました.

まとめ

上記2つのやり方どちらでもソートを行うことができます.

単純にソートをしたいだけならやり方2の方が早く書けると思います.

ただ優先度付きキューなどに入れたい場合やり方1を用いないとできないと思います.

参考

Python 独自クラスで比較演算ができるようにする

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

【音声生成+機械学習】ポケモンの鳴き声をWaveGANで生成する

はじめに

ゲーム「ポケットモンスター」シリーズにおいてはポケモンの鳴き声は電子音のようなSEとして表現されています。ほぼ全てのポケモンに固有の鳴き声が割り当てられており、本編をプレイしたことがある方にとっては馴染み深いものも多いと思われます。以下の動画はそれらの例です。





これらの音声をデータセットにし、機械学習の手法の1つ"WaveGAN"でポケモンの鳴き声を生成しました。以下は生成例です。


本記事ではこれに用いた手法を紹介していきます。

WaveGAN

音声の生成にはWaveGANという手法を使っています。これはGAN(Generative adversarial networks、敵対的生成ネットワーク)という手法をベースにしたもので、以下のように構成されています。
entire_architecture.png
この手法では、音声を生成するニューラルネットワーク(Generator)と、音声を識別するニューラルネットワーク(Discriminator)の2つを組み合わせます。

Generatorは音声データを生成し(これを偽物音声と呼ぶことにします)、それによってDiscriminatorをデータセット中の本物の音声データ(本物音声と呼ぶことにします)だと誤認させることを目指して学習を進めます。一方でDiscriminatorはGeneratorに騙されないよう、より正確に音声データの真贋を見極めるよう学習します。
二つのニューラルネットワークがお互いに鍛え合うことで、Generatorは徐々に学習データに近い音声を生成できるようになっていきます。
要するにGenerator VS Discriminatorです。

データセット

データセットには全国図鑑でNo.001のフシギダネ〜No.898のバドレックスまで全部のポケモンの鳴き声(.wav形式、サンプリングレート16000Hz)を用います。同じ種類のポケモンでもフォルムチェンジで鳴き声が変わるようであればそれもデータセットに含めます。ただしメガシンカ、ダイマックス時の鳴き声は学習全体に与える影響を考え除外しました。

Generatorの作成

Generatorの役割は、入力に乱数で構成された数列(これを乱数列と表現することにします)をとり、それを元にして音声を生成することです。生成した音声をDiscriminatorに入力した時、本物音声だと騙せるように学習していきます。
入力する乱数列の長さは20とし、出力する音声は長さ65536の時系列データ(約4秒程度)とします。入力を1次元転置畳み込み層(TransConv1D)に通していき最終的に音声を出力します。



Discriminatorの作成

Discriminatorは入力された音声が本物音声なのか、Generatorによって生成された偽物音声なのかを正しく識別できるよう目指して学習を進めます。要は音声認識器を作ります。
入力する音声は長さ65536の時系列データとし、出力はどれだけ本物らしいかを表す値(範囲は0~1)とします。入力を1次元畳み込み層(Conv1D)とPhase Shuffle(後述)に通していき最終的な判断結果を得ます。



checkerboard artifacts

GANによる画像生成において、Generatorが転置畳み込みによって出力した画像にはcheckerboard artifactsという、市松模様のような周期的なパターンが出現することがあります。




引用元 : Deconvolution and Checkerboard Artifacts


一部の特例をのぞいて、一般的にほとんどの学習データの画像においてはこのようなパターンは現れないため、Discriminatorはこの模様を含む画像が入力された時、それを偽物画像だと判定するように学習が進みます。

音声生成でもcheckerboard artifactsのようなパターン(以下ではこれを単にcheckerboard artifactsと呼ぶことにします)が生成されることがあり、ノイズのような形で知覚できるようです。音声においてcheckerboard artifactsは常に特定の位相でのみ発生するそうで、このことが原因でDiscriminatorが意図しない学習をしてしまい、結果的にモデル全体がうまく機能しなくなることがあるようです。

そこでDiscriminatorにPhase Shuffleという操作を行う層を導入します。

Phase Shuffle

checkerboard artifactsによってDiscriminatorが意図しない学習をしてしまうのを防止するため、Phase Shuffleを実行する層を導入します。

Phase Shuffleを行う層ではまず入力された特徴量に対し、$m$要素ずらす操作を実行します(ただし$m\in [-n,n]$、$n$はハイパーパラメーターで、今回は$n=2$に設定)。$m$はPhase Shuffleを実行するたびにランダムに$[-n,n]$の範囲から選び出す整数です。さらに、$m$要素ずらした結果空いた箇所をReflection Paddingによって埋めます。




Phase Shuffle層はDiscriminator中の、各畳み込み層の直後にそれぞれ配置します。


この操作によってDiscriminatorの学習がうまくいく理由として、WaveGANの論文中では以下のように言及されています。

"Intuitively speaking, phase shuffle makes the discriminator’s job more challenging by requiring invariance to the phase of the input waveform."
(直訳 : 直感的に言えば、phase shuffleは、入力波形の位相に不変性を要求することで、Discriminatorの仕事をより困難にします。)

自分的な解釈ですが、おそらくcheckerboard artifactsが常に特定の位相でのみ発生するところを、Phase Shuffleによってより多様な位相で発生するようにすることで意図通りの学習が進むようにしているものと思われます。

学習方法・誤差関数

GeneratorとDiscriminatorの損失関数はWGAN-GPとしました。WGAN-GPの定義は以下です。

  • Generatorの損失関数
-E[d_{fake}]
  • Discriminatorの損失関数
E[d_{fake}] - E[d_{real}] + \lambda E_{\substack{\hat{x}\in P_{\hat{x}}}}[(||\nabla_{\hat{x}}D(\hat{x})||_{2}-1)^{2}]

乱数列をGeneratorへ入力、ミニバッチの数$M$(今回は$M=16$と設定)だけ音声を生成します。それらをDiscriminatorに入力し、どれくらい本物らしいかを示す値をそれぞれの入力音声に対し$M$個出力させます。これを$d_{fake}$とします。
また、本物音声を$M$個Discriminatorに入力し、その時の$M$個の出力を$d_{real}$とします。$E$はミニバッチに対して平均をとる操作です。

Discriminatorの損失関数の第3項はgradient penaltyと呼ばれている項です。詳しい解説はこちら

誤差伝搬の最適化手法にはAdamを使い、学習率0.0001、Adamの一次モーメントと二次モーメント(モーメント推定に使う指数減衰率)はそれぞれ0.5と0.9に設定しました。

全体像

以上のようにして作成したGeneratorとDiscriminatorを図のように組み合わせ、WaveGANを構成します。
entire_architecture.png

学習+音声生成

今回はミニバッチ数16、エポック数は500に設定しました。
ポケモンの鳴き声を用いて学習を行い、Generatorに音声を生成させます。学習途中における出力は次のようになりました。

ソースコード

実装したコードはこのリポジトリにあります。
https://github.com/zassou65535/WaveGAN

余談

GANは本来音声の生成よりも、画像の生成や画像間の変換で有名な手法です。
GANによる画像生成や変換に関しても解説記事を書いているので是非こちらもどうぞ。

画像変換 :

画像生成 :

まとめ

WaveGANによって音声を生成しました。一般に画像に関する分野でよく使われている手法が、音声においても通用するのは興味深いですね。
機械学習で音声を生成する手法はWaveGANだけでなくSpecGAN、GANSynth、WaveNetなど多種多様な方法がこれまでに提案されています。いずれも長さ数秒程度の音声の出力が可能で、短めのサウンドエフェクトなどの生成に向いているかと思われます。皆さんもどんどん音声生成しましょう。

参考

Adversarial Audio Synthesis
PyTorch reimplementation of WaveGAN
Synthesizing Audio with Generative Adversarial Networks
Deconvolution and Checkerboard Artifacts

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

yukicoder contest 285 参戦記

yukicoder contest 285 参戦記

★2.5とはいえども4つもあればどれかは解けるんじゃないかと思って考えていたら意識が飛んで、起きたら25時過ぎだった(笑).

A 1415 100の倍数かつ正整数(1)

書くだけ……といいつつ、最初はちゃんと問題文を読んでいなくて、print(abs(N) // 100) で出して WA を食らった(馬鹿). まあ、100で割れば答えが出ますよね.

N = int(input())

if N < 0:
    print(0)
else:
    print(N // 100)

B 1416 ショッピングモール

一目で店員数が多い店から入れていけばいいことは分かるはず.

from math import log2

n, *A = map(int, open(0).read().split())

A.sort(reverse=True)

result = 0
for i in range(n):
    result += int(log2(i + 1)) * A[i]
print(result)

F 1420 国勢調査 (Easy)

コンテスト後に解いた. 連立一次方程式と同じで、関係する未知数のどれかが1個分かればドミノ倒しのように全部定まるので、まず Union Find で関係しているグループを求める. グループが求めれたらどれか一つを0として、ドミノ倒しで全部求めれば良い. 求まったら最後に矛盾したものがなかったかをチェックして、矛盾があったら -1 を、無かったら求まった答えを出力する.

from sys import setrecursionlimit, stdin


def find(parent, i):
    t = parent[i]
    if t < 0:
        return i
    t = find(parent, t)
    parent[i] = t
    return t


def unite(parent, i, j):
    i = find(parent, i)
    j = find(parent, j)
    if i == j:
        return
    parent[j] += parent[i]
    parent[i] = j


readline = stdin.readline
setrecursionlimit(10 ** 6)

N, M = map(int, readline().split())

parent = [-1] * N
d = {i: {} for i in range(N)}
vs = []
for _ in range(M):
    A, B = map(lambda x: int(x) - 1, readline().split())
    Y = int(readline())
    vs.append((A, B, Y))
    d[A][B] = Y
    d[B][A] = Y
    unite(parent, A, B)

result = [-1] * N
q = [i for i in range(N) if parent[i] < 0]
for i in q:
    result[i] = 0
while q:
    a = q.pop()
    for b in d[a]:
        if result[b] != -1:
            continue
        result[b] = result[a] ^ d[a][b]
        q.append(b)

for A, B, Y in vs:
    if result[A] ^ result[B] == Y:
        continue
    print(-1)
    exit()
print(*result, sep='\n')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む