20190719のPythonに関する記事は25件です。

pythonのprintで%dや%sを使う方法やformat文についてもメモ書き

はじめに

未来電子テクノロジーでインターンをしています、Sotaです。
プログラミング初心者のため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するので、どんどん指摘してください。

自分はc言語からプログラミングを勉強したので、%dや%fの表現に慣れています。
そのため、桁数指定などで、ストレスを感じたので、pythonでも%dや%fを使える表現をメモします。

pythonで%dや%sを使う

pythonでprintf文のような表現を使うには、
'文字列' % 変数 とすれば記述できます。
(例)

y = 10000
d = 100

print('100$は%d円' % y) #100$は10000万円
print('%d円は%d$' % (y, d)) #10000万円は100$

このような感じです。
もちろん文字列やfloat、桁数指定%.2fなども可能です。

format文

文字列メソッドformat()をでも似たような書き方ができます。
文字列中に置換フィールド{}が指定した引数と対応して置換されます。
文字列.format(変数)と書きます。
(例)

y = 10000
d = 100

print('100$は{}万円'.format(y)) #100$は10000万円
print('{}万円は{}$'.format(y, d)) #10000万円は100$
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonのprintで%dや%sを使う方法やformatメソッドについてもメモ書き

はじめに

未来電子テクノロジーでインターンをしています、Sotaです。
プログラミング初心者のため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するので、どんどん指摘してください。

自分はC言語からプログラミングを勉強したので、%dや%fの表現に慣れています。
そのため、桁数指定などで、ストレスを感じたので、Pythonでも%dや%fを使える表現をメモします。

Pythonで%dや%sを使う

Pythonでprintf関数のような表現を使うには、
'文字列' % 変数 とすれば記述できます。
(例)

y = 10000
d = 100

print('100$は%d円' % y) #100$は10000万円
print('%d円は%d$' % (y, d)) #10000万円は100$

このような感じです。
もちろん文字列やfloat、桁数指定%.2fなども可能です。

formatメソッド

文字列メソッドformat()でも似たような書き方ができます。
文字列中に置換フィールド{}が指定した引数と対応して置換されます。
文字列.format(変数)と書きます。
(例)

y = 10000
d = 100

print('100$は{}万円'.format(y)) #100$は10000万円
print('{}万円は{}$'.format(y, d)) #10000万円は100$
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeetCode / Length of Last Word

ブログ記事からの転載)

[https://leetcode.com/problems/length-of-last-word/]

Given a string s consists of upper/lower-case alphabets and empty space characters ' ', return the length of last word in the string.

If the last word does not exist, return 0.

Note: A word is defined as a character sequence consists of non-space characters only.

Example:

Input: "Hello World"
Output: 5

Discussionで'This problem is not fun at all'とか言われちゃってましたが、、、LeetCodeは個人的にはなかなか難易度高い問題も多いので、こういう箸休め的問題は欲しいなと思います。
英単語で構成された文の中で、末尾の単語の長さを返す問題です。

解答・解説

解法1

リストlsに対して、2つのポインタとなる変数slow, fastを用意し、slowは末尾の単語の末尾を、fastは末尾の単語の先頭のインデックスを返すように計算します。
末尾の単語の長さを判定するので、リストを逆向きにループさせていきます。
まず、slowは初期値を-1とします。list[-1]とするとlistの末尾の要素を示すのは自明ですね。そこから逆向きにループさせて、ブランクでない位置まで辿ります。
通常、'Hellow World'のように末尾の単語の後ろにブランクはなく、slowは初期値の-1のまま(ループは進まない)になることが多いと思います。

ls = len(ls)
slow = -1
while slow >= -ls and s[slow] == ' ':
    slow -= 1

次に、fastは初期値をslowと同値とします。そこから逆向きにループさせて、ブランクの位置まで辿ります。

fast = slow
while fast >= -ls and s[fast] != ' ':
    fast -= 1

最後に、slow - fastとすれば、末尾の単語の長さを計算できます。
まとめると以下のようになります。

class Solution:
    def lengthOfLastWord(self, s: str) -> int:
        ls = len(s)
        slow = -1
        while slow >= -ls and s[slow] == ' ':
            slow -= 1
        fast = slow
        while fast >= -ls and s[fast] != ' ':
            fast -= 1
        return slow - fast
解法2

1 line solutionを示して、今日は終わります。rstripで文末の余計なブランクを剥ぎ取ってしまうという力技。

class Solution:
    def lengthOfLastWord(self, s):
        return len(s.rstrip(' ').split(' ')[-1])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

水汲み問題(water jug problem)をPython、Javascriptで実装してみた

動機

研究室内の課題で水汲み問題を解く問題が出たので始めにPyhtonで書き、Javascriptの勉強中だったのでJavascriptでも書いてWebサイト風にアレンジしてみました。これをサーバーで公開するのもなぁと思ったのでコピペしてブラウザで開いてみてください。

Pythonのコード

warer_jug.py
import queue

class Bottle:
    def __init__(self,one,two):
        self.one = 0 #一個目のボトル
        self.two = 0 #二個目のボトル
        self.one_max = one
        self.two_max = two

    def put(self,now_one,now_two,num,past_list): #oneのボトルを水で満たす
        copy = past_list.copy()
        if num ==1:
            now_one = self.one_max 
        elif num ==2:
            now_two = self.two_max
        for i in range(len(copy)):
            if copy[i] != [now_one,now_two]:
                if (i== len(copy)-1):
                    copy.append([now_one,now_two])
                    queue.put(copy)
                    break
            else:
                break

    def pour(self,now_one,now_two,num,past_list): #numから一方のボトルに水を移す
        copy = past_list.copy()
        if num==1: #oneからtwo
            now_two += now_one
            if now_two >= self.two_max: #あふれる場合,全部は入れない場合
                now_one = now_two - self.two_max
                now_two = self.two_max
            else:
                now_one = 0
        elif num ==2: #twoからone
            now_one += now_two
            if now_one >= self.one_max: #あふれる場合,全部は入れない場合
                now_two = now_one - self.one_max
                now_one = self.one_max
            else:
                now_two = 0
        for i in range(len(copy)):
            if copy[i] != [now_one,now_two]:
                if (i== len(copy)-1):
                    copy.append([now_one,now_two])
                    queue.put(copy)
                    break
            else:
                break

    def away(self,now_one,now_two,num,past_list): #水を捨てる
        copy = past_list.copy()
        if num==1:
            now_one = 0
        elif num==2:
            now_two = 0
        for i in range(len(copy)):
            if copy[i] != [now_one,now_two]:
                if (i== len(copy)-1):
                    copy.append([now_one,now_two])
                    queue.put(copy)
                    break
            else:
                break

past_op = []
queue = queue.Queue()
num_one = int(input("Bottle1? ")) 
num_two = int(input("Bottle2? "))
num_Want = int(input("How much do you want? "))

bottle = Bottle(num_one,num_two)
bottle.one = bottle.one_max
past_op.append([bottle.one,bottle.two])
queue.put(past_op)

while (True):
    get_queue = queue.get()
    now = get_queue[-1]
    bottle.one,bottle.two = now[0],now[1]
    if (bottle.one == num_Want or bottle.two == num_Want):
        for i in get_queue:
            print(i)
        break
    if bottle.one > 0 and num_one > bottle.one:
        bottle.put(bottle.one,bottle.two,1,get_queue)
        bottle.pour(bottle.one,bottle.two,1,get_queue)  #oneからtwo
        bottle.away(bottle.one,bottle.two,1,get_queue) #oneを捨てる
    if bottle.one == 0:
        bottle.put(bottle.one,bottle.two,1,get_queue)
    if bottle.one == num_one:
        bottle.pour(bottle.one,bottle.two,1,get_queue)  #oneからtwo
        bottle.away(bottle.one,bottle.two,1,get_queue)

    if bottle.two > 0 and num_two > bottle.two:
        bottle.put(bottle.one,bottle.two,2,get_queue)
        bottle.pour(bottle.one,bottle.two,2,get_queue)  #oneからtwo
        bottle.away(bottle.one,bottle.two,2,get_queue) #oneを捨てる
    if bottle.two == 0:
        bottle.put(bottle.one,bottle.two,2,get_queue)
    if bottle.two == num_two:
        bottle.pour(bottle.one,bottle.two,2,get_queue)  #oneからtwo
        bottle.away(bottle.one,bottle.two,2,get_queue)
    if(queue.empty()):
        print("無理です")
        break

はい、めちゃくちゃ長いです。頑張ればもうちょい短くなると思います。
やってることはBottleクラスでボトル1と2の入っている水量とサイズを定義し、水を入れる関数と水を移す関数、水を捨てる関数を定義します。
あとはキューに今までの水の推移を表したリストを入れていき、ボトル1か2が目的の水量になったらその推移を表したリストを表示して終了です。
もし不可能な組み合わせ(例:6Lと3Lの容器で4Lを測るなど)をしたらいずれキューが空になるのでそうなったら無理と表示して終わりです。

Javascript(+HTML+CSS)のコード

water_jug.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>水量を測る</title>
    <h1>水を測ってみよう!</h1>
    <style type="text/css">
        body{
            text-align: center;
        }
        #table{
            position: relative;
        }
        #table table{
            position:absolute;
            left: 50%;
        }
    </style>
    <script>
        var queue = [];
        var past_op = [];
        function Bottle(one,two){
            this.one = 0;
            this.two = 0;
            this.one_max = one;
            this.two_max = two;
        }
        function show(list_water){
                var table = document.getElementById("table");
                var string = "<table><tr><th>ボトル1</th><th>ボトル2</th></tr><br>";
                for(var i = 0; i<= list_water.length-1; i++){
                    string += "<tr><td>"+list_water[i][0]+"</td><td>"+list_water[i][1]+"</td></tr><br>";
                }
                string +="</table><br>";
                table.innertHTML = string;
        }
        function calc_water(myForm) {

            var bottle = new Bottle(Number(myform.Bottle1.value),Number(myform.Bottle2.value));
            bottle.one = bottle.one_max;
            past_op.push([bottle.one,bottle.two])
            queue.push(past_op)

            while (1){
                var get_queue = queue.shift()
                var now = get_queue[get_queue.length -1]

                bottle.one = now[0];
                bottle.two = now[1];
                if (bottle.one == Number(myform.Want_Water.value) || bottle.two == Number(myform.Want_Water.value)){
                    for (var i=0; i < get_queue.length; i++){
                        console.log(get_queue[i]);
                    }
                    show(get_queue);
                    break;
                }
                if (bottle.one > 0 && bottle.one_max > bottle.one){
                    bottle.put(bottle.one,bottle.two,1,get_queue);
                    bottle.pour(bottle.one,bottle.two,1,get_queue);  //oneからtwo
                    bottle.away(bottle.one,bottle.two,1,get_queue); //oneを捨てる
                }
                if (bottle.one == 0){
                    bottle.put(bottle.one,bottle.two,1,get_queue);
                }
                if (bottle.one == bottle.one_max){
                    bottle.pour(bottle.one,bottle.two,1,get_queue);  //oneからtwo
                    bottle.away(bottle.one,bottle.two,1,get_queue);
                }
                if (bottle.two > 0 && bottle.two_max > bottle.two){
                    bottle.put(bottle.one,bottle.two,2,get_queue);
                    bottle.pour(bottle.one,bottle.two,2,get_queue);  //oneからtwo
                    bottle.away(bottle.one,bottle.two,2,get_queue); //oneを捨てる
                }
                if (bottle.two == 0){
                    bottle.put(bottle.one,bottle.two,2,get_queue);
                }
                if (bottle.two == bottle.two_max){
                    bottle.pour(bottle.one,bottle.two,2,get_queue);  //oneからtwo
                    bottle.away(bottle.one,bottle.two,2,get_queue);
                }
                if (queue.length == 0){
                    cant();
                    break;
                }
            }
        }
        Bottle.prototype.put = function(now_one,now_two,num,past_list){
            var copy = Array.from(past_list);
            if (num ==1){
                now_one = this.one_max;
            }
            else if (num ==2){
                now_two = this.two_max;
            }
            for (var i=0;i< copy.length;i++){
                if (past_list[i].toString() != [now_one,now_two].toString()){
                    if (i==copy.length-1){
                        copy.push([now_one,now_two]);
                        queue.push(copy);
                        break;
                    }
                }
                else{
                    break;
                }
            }
        }
        Bottle.prototype.pour = function(now_one,now_two,num,past_list){
            var copy = Array.from(past_list)
            if (num==1) {
                now_two += now_one;
                if (now_two >= this.two_max){ //あふれる場合,全部は入れない場合
                    now_one = now_two - this.two_max;
                    now_two = this.two_max;
                }
                else {
                    now_one = 0;
                }
            }
            else if (num ==2){ //twoからone
                now_one += now_two;
                if (now_one >= this.one_max){//あふれる場合,全部は入れない場合
                    now_two = now_one - this.one_max;
                    now_one = this.one_max;
                }
                else{
                    now_two = 0;
                }
            }
            for (var i=0;i< copy.length;i++){
                if (past_list[i].toString() != [now_one,now_two].toString()){
                    if (i==copy.length-1){
                        copy.push([now_one,now_two]);
                        queue.push(copy);
                        break;
                    }
                }
                else{
                    break;
                }
            }
        }
        Bottle.prototype.away = function(now_one,now_two,num,past_list){ //水を捨てる
            var copy = Array.from(past_list)
            if (num==1){
                now_one = 0;
            }
            else if (num==2){
                now_two = 0;
            }
            for (var i=0;i< copy.length;i++){
                if (past_list[i].toString() != [now_one,now_two].toString()){
                    if (i==copy.length-1){
                        copy.push([now_one,now_two]);
                        queue.push(copy);
                        break;
                    }
                }
                else{
                    break;
                }
            }
        }  
    </script>
</head>
<body>
    <form id="myform" name="myform"><p>
            Bottle1の大きさ:<input type="text" name="Bottle1"><br>
            Bottle2の大きさ:<input type="text" name="Bottle2"><br>
            欲しい水の量:<input type="text" name="Want_Water"><br> 
            <input type="button" value="計算" 
                name="calc" onclick="calc_water(this.form)"><br>  
    </p></form>
    <div id = "table">
    </div>
</body>
</html>

javascriptは勉強して1週間でクソザコナメクジなのでもっといいコードが書けるかもしれません
やってることはpythonとほとんど同じです。解が見つかったら表を作成するHTMLコードを作成し表示するって感じです。
質問またはこうした方がいいってのがあったら気軽にコメントしてください

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

PythonでGoogleスプレッドシートに結合cellを作成する方法

Motive

単にスプレッドシートにデータを入力したい場合は gspread ライブラリを使えば sheet.update_acell('A1', "AAA") でよいし、 cellに線や色を付ける、文字のタイプ、文字フォント、または文字寄せなどを設定する場合は gspread-formatting を使って

import gspread_formatting as gsformat

fmt = gsformat.cellFormat(
    backgroundColor=gsformat.color(0.0, 0.5, 0.0),
    textFormat=gsformat.textFormat(foregroundColor=gsformat.color(0, 0, 0)),
    horizontalAlignment='LEFT',
)

gsformat.format_cell_range(sheet, 'A1:A10', fmt)

とすると反映は可能です。

ですが、
A1:A5までcellを結合したい!
思った場合、gspreadgspread-formattingにそれっぽいメソッドはないです。

レイアウトを整えるのは重要事項なのでもっとコードでなんとかしたい!:dancer::100:

と思った時は、下記の例はresponseで送るjson形式に似たものを用いる方法をまとめたので記述します。:japanese_goblin:

Method

対処法としてですが gspreadbatch_update(dict) メソッドを用います。

code

value.json
{
    "requests" : [
        {
            "mergeCells": {
                "range": {
                    "startRowIndex": 0,
                    "endRowIndex": 1,
                    "startColumnIndex": 0,
                    "endColumnIndex": 5
                },
                "mergeType": "MERGE_ALL"
            }
        }
    ]
}
import configparser
import json
import os
import json
from oauth2client.service_account import ServiceAccountCredentials
import gspread

def main():

    config = configparser.ConfigParser()
    ini_file = os.path.join("./", 'setting.ini')
    config.read(ini_file)

    scope = ["https://spreadsheets.google.com/feeds",
            "https://www.googleapis.com/auth/drive"]
    book_id = config.get("googleSpreadSheet", "book_id")
    path = os.path.join("./", config.get("googleSpreadSheet", "keyfile_name"))
    credentials = ServiceAccountCredentials.from_json_keyfile_name(path, scope)        
    client = gspread.authorize(credentials)
    gfile = client.open_by_key(book_id)

    dst = {}
    with open("value.json", encoding='utf-8') as fin:
        dst = json.load(fin)

    gfile.batch_update(dst)

if __name__ == "__main__":
    main()

これで A1:A5 のセルが結合されました。
image.png

Future

batch_update メソッドはcell結合以外にもcopy&pasteやsortもできるみたいで、内部ではPOST https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}:batchUpdateにリクエストをかけています。ヒントは(https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request)
にあります。

Reference

gspread
gspread-formatting
PythonでGoogleSpreadSheetのセルにコメントを付加したり色や枠線をつける
Basic Formatting
Method: spreadsheets.batchUpdate

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

Goかpythonでブロックチェーンを理解する

 最近ビットコインがまた上昇しているという話ですね。五月あたりに一気に上昇したとかで私のTwitterのTLにいるビットコインの取引している人はめちゃめちゃ喜んでました。そんなわけで今回はブロックチェーンがどのような仕組みになっているかGoとpython(Goとpythonで同じ内容を実装します。所属学部の関係上、pythonが読める人が友達に多いので)で実装しながら説明していきます。最後にまあ最低限ブロックチェーンのシミュレーションといっても差し支えないだろうというもの(主観です)をGoで実装します。

ブロックチェーンとは何か

ブロックチェーン-Wikipedia

ブロックチェーンとは、分散型台帳技術、または、分散型ネットワークである。ビットコインの中核技術(サトシ・ナカモトが開発)を原型とするデータベースである。

とあるように新しい種類のデータベースです。(これは私の考えです。でもたぶんあってます。)よく知られている応用例としてbitcoinやethereumなんかがあります。基本的にbitcoinはお金の記録を保存するもの、ethereumはそれに加えてプログラムの保存をするもののように考えています。ただ、近年はbitcoinにもethereumと同じような機能が実装されてきているらしいです。ブロックチェーンと聞くと「インターネット上にあるお金」を想像する方もいらっしゃると思うんですけど、ブロックチェーンの技術自体はお金とは無関係です。しかしその防御力の高さからかお金の保存や取引などに利用されます。(嘘です。みんながお金をかけても掘りたくなるような実装が組み込まれてるんですけど、それは今回はやりません。)

ブロックチェーンの構造

ハッシュ

ブロックチェーンを理解するのに欠かせない知識の一つにハッシュというものがあります。

ハッシュ-Wikipedia

ハッシュ、ハッシュ値 - データから算出した小さな値。各データを区別・表現する目的に用いる。

>>certutil -hashfile hogehoge.txt SHA256

windowsのコマンドプロンプトでこのように入力するとファイルのハッシュ値を計算することができます。ハッシュ値を算出するための関数をハッシュ関数といいます。ハッシュ関数は出力された値から入力された値を逆算することができないという特徴を持ちます。二つの異なるデータのハッシュ値は互に異なるように設計されているため(必ずしも違う値が出力されるわけではない)、データのハッシュ値が違う場合、互いのデータが違う値であるということが言えます。パスワードの保管などセキュリティが重視される場合、ハッシュ関数にはハッシュの衝突(別のデータのハッシュ値を計算したときに同じ値が出力される現象)ができるだけ少ないSHA(secure hash algorithmの略)と呼ばれる種類のハッシュ関数が使われます。

*ハッシュ値は5次関数に値を代入したらすぐに答えが求まるのに対して5次方程式は解けないというのとイメージは同じです。(たぶん)

基本構造

ブロックチェーンの仕組み1.png

ブロックチェーンの基本構造は上の図のように(わかりにくかったらごめんなさい)あるブロックはその前のブロックのハッシュ値をもっているというリンクリストのような構造になっています。先頭は左のブロックです。ブロックチェーンの名前の由来はここです。これで何がうれしいかを説明します。まず、攻撃者が先頭のブロックのデータを書き換えたとします。そうすると次のブロックの中にある先頭のブロックのハッシュ値と、書き換えられた先頭のブロックのハッシュ値が一致しなくなります。そこで攻撃者は矛盾点を消すため二番目のブロックのハッシュ値を書き換えます。すると今度は二番目と三番目のブロックの間に矛盾が生じます。矛盾を消すために攻撃者が三番目のブロックを書き換えます。このように情報を書き換えようと攻撃した場合、書き換えたブロックから一番最後のブロックまですべてのブロックの情報を書き換えなければならないことになります。とりあえずここまでで攻撃するのはいっぱい書き換えなきゃいけないしなんか大変そうだなあってことがわかっていただければよいです。

Goでプログラムを書いてみる

ここまでの説明をプログラムにしてみます。

まずはブロックの構造体とブロックを格納する配列(ブロックチェーン)を定義します。

//後で使うパッケージをimportしとく
package main

import
(
    "fmt"
    "bytes"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
)

//ブロックの構造体
type block struct {
    Prevhash [32]byte
}
//都合がいいのでハッシュ値を入れるとブロックを作ってくれる関数も作っとく
//ブロックを作成
func makeBlock(hash [32]byte) block{
    var a block
    a.Prevhash = hash
    return a
}

//ブロックチェーン
//最初のブロックのハッシュ値は0にしとく
var chain = []block{makeBlock([32]byte{})}

次にブロックをjson文字列にする関数と[]byteを渡すとSHA256のハッシュ値を計算してくれる関数を定義

//ブロックをjson文字列に変換する
func convjson(b block) string{
    var buf bytes.Buffer
    byt, _ := json.Marshal(b)
    buf.Write(byt)
    return buf.String()
}

//ハッシュ値を計算する関数
func hash(str []byte)  [32]byte {
    return [32]byte(sha256.Sum256(str))
}

最後にブロックをつなげてく関数をつくって終了!

//マイニング
func mine() string{
    lastBlock := chain[len(chain) - 1]
    lastBlock_json := convjson(lastBlock)
    n := makeBlock(hash([]byte(lastBlock_json)))
    chain = append(chain,n)
    return convjson(n)
}

//ハッシュ値はbyteの配列で保存されています。つまり1byteごとの数字の羅列
//かっちょよく16進数に変換したい場合はこの関数も定義しときましょう。
//[32]byteを[]byteに変換して16進数文字列に変換する
func convhex(b [32]byte) string{
    by := make([]byte,32)
    for i:=0;i<32;i++{
        by[i] = b[i]
    }
    return hex.EncodeToString(by)
}

ホリホリ

func main(){
    //マイニングの回数
    n := 5
    for i:=0;i<n;i++{
        mine()
    }
    fmt.Println(chain)
    /*
    さっきのconvhex関数を定義してたらこっちの方がカッコイイ
    for i:=0;i<len(chain);i++{
        fmt.Println(convhex(chain[i].Prevhash))
    }
     */
}

pythonでプログラムを書いてみる

pythonはGoよりもいくらか簡単にプログラムを書くことができます。すごく短いので説明はコメントアウトしてちょちょっと書きます。さっきと違うこととしては、ブロックを構造体ではなくdictionary(辞書)で作りました。

from hashlib import sha256 # ハッシュ関数
import json #dictionaryをjsonの文字列に変換するため

class blockChain():
    def __init__(self):
        self.chain = [{"prevhash":'0'*64}] # 初期値は0に
    #ディクショナリ、辞書、連想配列(いろんな呼び方があります。)
    #それでブロックを作る関数
    def makeBlock(self,hash):
        b = {} #新しいブロックをdictionaryで
        b["prevhash"] = hash
        return b
    #ブロックののjsonを取得しハッシュ値を計算する
    def hash(self,map):
        data = json.dumps(map) #dictionaryをjsonに変換
        #文字列をエンコードしてハッシュ値を計算して十六進数の文字列に変換
        hash = sha256(data.encode()).hexdigest()
        return hash
    #ブロックをつなげていく
    def mine(self):
        lastBlock = self.chain[-1] # 一番最後のブロックを取得
        hash = self.hash(lastBlock) # ハッシュ値を計算
        nextBlock = self.makeBlock(hash) # 新しいブロックを作る
        self.chain.append(nextBlock) # ブロックをつなげる

c = blockChain()
for i in range(5):
    c.mine()
print(c.chain)

こんな感じで書けます。

ブロックチェーンをより安全にする

上では、ただただ前のブロックのハッシュ値を計算するだけでブロックをつなげていきました。しかし、ハッシュ値を計算するのは一瞬です。これだけの速度でチェーンを伸ばしていけると全て書き換えるのはかんたんです。なので、実際のブロックチェーンは一工夫されています。
2.png

上の部分はハッシュ値の計算からブロックの生成までのフローチャートです。前回の実装ではハッシュ値を計算したらそのままブロックを作成しています。しかし、工夫した実装ではハッシュ値に制限をもうけています。性能の良いハッシュ関数は出現する値の確率が一様です。ですので、制限を設けてあげるとそのハッシュ値が出るまで何回も何回も計算しなければいけません。ただし、同じデータについてハッシュ値を計算しても同じ値が出力されるだけです。ですので、ブロックに自由に変更できる要素を追加してその要素をごちゃごちゃ変えて条件を満たすハッシュ値を探していきます。
ナンス含.png
チェーンは上の図のようになります。これも先頭は左のブロックです。ハッシュ値は入力される値が少し変わるだけで大きく変動するのでごちゃごちゃ値をこねくり回して条件にあうものをさがします。ハッシュ値の制限を厳しくすればするほど合う値を探すのに時間がかかります。こうすることでブロックを改変するのに非常に大きなコストがかかって攻撃が受けにくくなります。

*ビットコインでは1ブロックつなげるのに平均10分程度かかるようにハッシュ値の制限がかかっています。また、時期によって多くの人が掘る場合やあまり掘られない場合があるので制限がちょうどいいくらいになるように2週間程度に一回制限が修正されます。

Goでプログラムを書いてみる

【変更】まず、構造体にごちゃごちゃ値を変えてハッシュ値を探す値を追加します。この値は一般的にNonceと呼ばれるので変数名にそれを使います。ブロックチェーンの初期値にnonceを追加しときます。

//ブロックチェーン
var chain = []block{makeBlock([32]byte{},0)}

//ブロックチェーンの中身の構造体
type block struct {
    Prevhash [32]byte
    Nonce int //ごちゃごちゃ変える値
}

//ブロックを作成
func makeBlock(hash [32]byte,nonce int) block{
    var a block
    a.Prevhash = hash
    a.Nonce = nonce
    return a
}

【追加】閾値のバイト配列と二つのバイト配列を比較する関数を設定します。aに算出したハッシュ値、bに閾値を入れるとa<bのとき1が返されます。

//256bitのバイト列の比較 a < bのとき1
func comper(a [32]byte,b []byte) int{
    check := make([]byte,32)
    for i:=0;i<32;i++{
        check[i] = a[i]
    }
    return bytes.Compare(b,check)
}

閾値のバイト配列を生成します。指数の計算を今回は32bitで収まる範囲の整数の計算しかしないのでこんな感じで書いてますが、たぶんmathとかのパッケージにあると思うので使うといいと思います。


//指数
func pow(n int,m int) int{
    if m == 1{
        return n
    }else if m == 0 {
        return 1
    }else if m % 2 == 0{
        k := pow(n,m/2)
        return  k*k
    }else {
        return n*pow(n,m-1)
    }
}
//2^nの256bitのバイト列を生成
func makebyte(n int) []byte{
    ans := make([]byte,32)
    //256以上の値が引数に渡されたら制限がないといってよいので
    //とりあえず一番大きい桁に255入れときます。
    //(凝る人はすべてに255入れるループ書いても良いよ)
    if n >= 256{
        ans[0] = byte(255)
        return ans
    }
    amari := n % 8
    syo := n / 8
    check := pow(2,amari)
    ans[31-syo] = byte(check)
    return ans
}

【変更】mine関数のなかで、望んだ範囲のハッシュ値が算出されるまで何度もnonceを変更しながら計算するようにコードを変更します。

//マイニング diffが小さいほど制約が大きい
func mine(diff int) string{
    lastBlock := chain[len(chain) - 1]
    lastBlock_json := convjson(lastBlock)
    lastBlock_hash := hash([]byte(lastBlock_json))
    counter := 0
    limit := makebyte(diff)
    var n block
    for {
        n = makeBlock(lastBlock_hash,counter)
        nowBlock_json := convjson(n)
        nowBlock_hash := hash([]byte(nowBlock_json))
        if comper(nowBlock_hash,limit) == 1{
            break
        }
        counter ++
    }
    chain = append(chain,n)
    return convjson(n)
}

動かしてみます。

func main(){
    //マイニングの回数
    n := 5
    for i:=0;i<n;i++{
        block := mine(240) //難易度を240にしてホリホリ
        fmt.Println(string(block))
    }
}

出力
{"Prevhash":[205,238,133,119,21,139,235,115,177,165,36,115,169,188,168,141,124,99,113,3,149,95,2,232,207,114,60,12,88,69,28,28],"Nonce":280}
{"Prevhash":[0,0,6,23,177,48,240,93,239,0,196,205,228,167,78,52,50,128,27,144,125,8,172,233,27,33,169,127,40,252,55,220],"Nonce":18792}
{"Prevhash":[0,0,93,198,62,162,247,57,110,183,62,209,236,28,195,179,116,88,163,84,1,80,249,49,38,43,253,165,176,14,1,124],"Nonce":7932}
{"Prevhash":[0,0,232,216,132,117,74,174,19,142,116,9,172,50,160,160,64,209,74,47,215,0,182,22,132,178,3,178,166,254,253,70],"Nonce":62563}
{"Prevhash":[0,0,57,142,21,48,208,108,153,110,201,129,204,153,207,154,101,222,46,77,167,82,189,90,50,115,19,20,226,196,53,90],"Nonce":95247}

難易度をかえて掘るのに時間がかかることを確認してみてください。

pythonでプログラムを書いてみる

【変更】さっきのmakeBlock関数にnonce値を加えます。nonce値はごちゃごちゃ変えて欲しいハッシュ値を探す値です。

def makeBlock(self,hash,nonce):
    b = {}
    b["prevhash"] = hash
    b["nonce"] = nonce
    return b

【変更】mine関数の引数にdiffという引数を追加します。これは、ハッシュ値の閾値です。ハッシュ値が2^diffよりも小さい値だった場合はブロックをつなげます。違う場合はnonceを変えてハッシュ値を計算しなおします。

def mine(self,diff):
    lastBlock = self.chain[-1]
    hash = self.hash(lastBlock)
    counter = 0
    while True:
        nextBlock = self.makeBlock(hash,counter)
        if int(self.hash(nextBlock),16) < 2**diff:break
        counter += 1
    self.chain.append(nextBlock)
    return nextBlock

実行!

c = blockChain()
n = 5
for i in range(n):
    block = c.mine(240)
    print(block)

出力
{'prevhash': 'fb197130829d450616b62e431f55fb639aa7ba5989dc82282aeb7b3435856677', 'nonce': 76517}
{'prevhash': '0000c25cb0503255c52ad386cde40b418ec7e0b876dde7cb40039195621c584d', 'nonce': 60994}
{'prevhash': '0000c4180e4cdc8bfaf24ec8738af39800bd08ef9926ab47a56a203194408276', 'nonce': 60232}
{'prevhash': '000030a575effc930f2c4d8529b39ad0e3052853e01b9d3e277dce3cd560877f', 'nonce': 240649}
{'prevhash': '000018c5d3e7bab0564d1b8dce874ed1efd0722c79427709a7ea304537b6cae0', 'nonce': 102298}

ブロックチェーンが安全である理由

ここまでで、新しいブロックを生成するには非常に大きなコストがかかることがわかりました。しかし、これだけでは時間をかけて計算すればすべてのブロックを改変してしまうことができてしまうと思うと思います。ここで、Aさん、Bさん、Cさん、Dさんの四人がSumiSumicoinという暗号通貨をマイニングするときを考えます。四人が掘り始めていくらか時間が経ちました。その時に四人それぞれが掘り進めたブロックのチェーンが下のようになっているとします。
コンセンサス.png
このとき、全員のブロックが違ってはいけないので全員のブロックを統一します。結論から言いますと、統一するために一番長くチェーンをつなげた人のブロックを採用します。今回の場合でいくとBさんのブロックが採用されて、Aさん、Cさん、DさんはBさんのつなげたブロックをコピーします。また、ブロックのつながりに矛盾が生じるもの(ハッシュ値が一致しないもの)は採用しないようにします。このようにすることで、攻撃者がブロックの改ざんをする場合にはほかのまじめにマイニングしている人全員よりも速い速度でブロックをつなげる必要が出てきます。つまり、攻撃者はすべてのマイニングをする人の総和よりも多くの計算リソースを用意する必要が出てくるためそれはほぼ不可能です。私はそれだけ多くのコンピューターを用意して攻撃して採算が取れるとは思いません。まともな人間ならまずやらないでしょう。(個人的な意見です)

この後は複数人でマイニングをした場合を一つのパソコンでシミレーションをするということを行います。ですが、なかなかに長いコードになってしまい、型のないpythonで書くのは辛くなってしまったのでGoのみの実装となります。ご了承ください。

Goで実装してみる

上にも書いたように複数人でマイニングをする場合を実装します。並列処理を実装することであたかも複数人でマイニングをしているように見せかけることができます。

とりあえずこんだけのパッケージをimportしとくと良いです。

import
(
    "math/rand"
    "fmt"
    "reflect"
    "bytes"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "time"
    "os"
)

まずはブロックの構造を変更します。複数人で掘るということで、マイナーの名前をブロックに記録することにします。また、他人のチェーンが整合性が取れているかどうか判別するためにハッシュ値の制限の閾値を入れてあげます。その変更に合わせてブロックを作成する関数も変更します。

//ブロックチェーンの中身の構造体
type block struct {
    Prevhash [32]byte
    Nonce int
    Difficulty int //難易度
    Name string //マイナーの名前
}

//ブロックを作成
func makeBlock(hash [32]byte,nonce int,difficulty int,name string) block{
    var a block
    a.Prevhash = hash
    a.Nonce = nonce
    a.Difficulty = difficulty
    a.Name = name
    return a
}

今回はブロックチェーンを複数人で掘ります。ですので、人数分のチェーンが必要になります。これに合わせてチェーンを初期化する関数も用意します。

//ブロックチェーン
//var chain = []block{makeBlock([32]byte{},0)}
//チェーンのリスト
var list = make([][]block,4)
//初期化
func initList(){
    for i:=0;i<4;i++{
        list[i] = []block{makeBlock([32]byte{},0,256,"")}
    }
}

チェーンの整合性が取れているかどうかを判別します。これは、他人が悪意のあるチェーンを作った時に自分のチェーンと間違えて置き換えないようにするためです。

//チェーンの整合性をチェック
func check(ls []block) bool{
    //前後のブロックとハッシュ値の関係に矛盾がないかどうか
    for i:=0;i<len(ls) - 1;i++{
        //前のブロックのハッシュ値
        before := hash([]byte(convjson(ls[i])))
        //次のブロックに書いてある前のブロックのハッシュ値
        after := ls[i+1].Prevhash
        if ! reflect.DeepEqual(after,before){
            return false
        }
    }
    //ハッシュ値が2^difficultyよりも小さいかどうか
    for i:=0;i<len(ls);i++{
        //制限のバイト配列
        limit := makebyte(ls[i].Difficulty)
        //現在のブロックのハッシュ値
        nowBlock_hash := hash([]byte(convjson(ls[i])))
        if comper(nowBlock_hash,limit) != 1{
            fmt.Println("yei")
            return false
        }
    }
    return true
}

一番長くて整合性のあるチェーンを返す関数です。ポインタだけ渡されるみたいなことがあると怖いので、最後にコピー走らせてます。

//全てのチェーンを調べて一番長くて整合性のあるチェーンを返す
func consensys() []block{
    //チェーンの長さを入れる。整合性のないチェーンの場合は-1
    chainLength := make([]int,len(list))
    for i:=0;i<len(list);i++{
        if check(list[i]){
            chainLength[i] = len(list[i])
        } else {
            chainLength[i] = -1
        }
    }
    maxLength := 0
    maxIndex := 0
    for i:=0;i<len(chainLength);i++{
        if chainLength[i] > maxLength{
            maxLength = chainLength[i]
            maxIndex = i
        }
    }
    ansChain := make([]block,maxLength)
    for i:=0;i<maxLength;i++{
        ansChain[i] = list[maxIndex][i]
    }
    return ansChain
}

mine関数も変えます。まず、1から順に探していたnonce値を乱数にします。これは、複数の人間が全く同じようにnonce値を探していたらちょっと早く掘り始めた人の一人勝ちになってしまうからです。(たぶん)実験はしてません。たぶんそうです。たぶん...
また、ひとブロック掘るごとに一番長いチェーンを自分のチェーンに置き換えます。この実装だと長いチェーンが自分だとしても自分のチェーンをコピーして置き換えるという操作をしているので凝る人は高速化できると思います。引数のindexはさっき作ったチェーンのリストのインデックスを指定してください。

//マイニング diffが小さいほど制約が大きい indexはチェーンのリストの何番目か
func mine(diff int,index int,name string) string{
    lastBlock := list[index][len(list[index]) - 1]
    lastBlock_json := convjson(lastBlock)
    lastBlock_hash := hash([]byte(lastBlock_json))
    limit := makebyte(diff)
    var n block
    for {
        //                          乱数にする↓
        n = makeBlock(lastBlock_hash,rand.Int(),diff,name)
        nowBlock_json := convjson(n)
        nowBlock_hash := hash([]byte(nowBlock_json))
        if comper(nowBlock_hash,limit) == 1{
            break
        }
    }
    //一番長いチェーンを自分の所に置き換える
    list[index] = append(list[index],n)
    list[index] = consensys()
    return convjson(n)
}

最後にホリホリするためのmain関数を書きます。A、B、C、Dの四人でマイニングをします。それぞれmine関数に名前とインデックスを指定します。また、もう一つ5秒おきに最長のチェーンの状態を出力してくれるものも並列で実行しときます。

func main(){
    initList()
    //Aさん
    go func() {
        for {
            mine(235,0,"A")
        }
    }()
    //Bさん
    go func() {
        for {
            mine(235,1,"B")
        }
    }()
    //Cさん
    go func() {
        for {
            mine(235,2,"C")
        }
    }()
    //Dさん
    go func() {
        for {
            mine(235,3,"D")
        }
    }()

    //五秒おきに最長チェーンの状態を出力
    go func() {
        for {
            time.Sleep(5*time.Second)
            ls := consensys()
            for i:=0;i<len(ls);i++{
                fmt.Println(ls[i])
            }
        }
    }()
    //四人で5分マイニングした後最長チェーンの状態をファイルに書き込む
    time.Sleep(300*time.Second)
    //                     ファイル名を指定↓
    file, err := os.Create(`directory\\hogehoge.txt`)
    if err != nil {
        // Openエラー処理
    }
    defer file.Close()

    ls := consensys()
    for i:=0;i<len(ls);i++{
        output := convjson(ls[i]) + "\r\n"
        file.Write(([]byte)(output))
    }
}

ここまで出来たら実行してあげましょう。五分後には自作ブロックチェーンの結果がファイルに出力されているはずです。私のPC(2コア4スレッド2.50GHz - 2.71GHz)では235がちょうどよい難易度だったのでそれになってまずが、もっといいプロセッサが積んであるコンピューターではもう少し数字を下げてあげてもいいかもしれません。

私が出力したやつ

このソースコード

最後に

ここまで作ってきましたが、やはりブロックチェーンの構造は面白いですね。これでとりあえず変更されにくいチェーンを組めたので、あとはブロックの中にどんな機能を入れるかによってさまざまな性質のブロックチェーンを作ることができます。世の中にたくさんの暗号通貨が存在しているのはそのためです。(ほかにさして知識のない人が自分の通貨を作りたいと言って作る人もいますが...)なんにせよ、ブロックチェーンは未来ある技術なので今後の展開が楽しみです。あと、私はこれからテスト期間に入るので勉強頑張ります。

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

pythonで定時実行するAWS Lambda処理を作った

はじめに

相変わらず競馬関係のプログラムを書いています。
これまではHeroku上で開発していたのですが、仕事でAWSを使っていることもあり、個人開発でもAWSを使い始めました。

Herokuだと毎日決まった時間に動作する処理を書く場合、Heroku Schedulerを使う形になりますが、AWSだとLambdaをCloudWatchイベントでキックする形…とはいうもののひとまとめになっている情報が見当たらなかったので、やってみたことを残しておきたいと思います。

※2019年7月時点の情報です。AWSはサービス改善のスピードが速すぎてWebにまとめた情報があっという間に陳腐化するので、そこはご注意を…

AWS SAMのテンプレート

AWS SAMを使ってみたのですが、元になるテンプレートを作成するためにsam initコマンドを使うとAPIゲートウェイからキックされる処理のテンプレートになります。

定時実行する処理用のテンプレートは…ということで調べたところ、以下にありました。
CloudWatch イベント アプリケーションの AWS SAM テンプレート

だけど、これは定時実行ではなくて定期実行、かつ今回必要ないようなリソースも使っていたりだったので、定期実行する最低限のテンプレートを作りました。

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  notify_hitokuchi

  Sample SAM Template for notify_hitokuchi

Globals:
  Function:
    Timeout: 10

Resources:
  NotifyHitokuchiFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: notify_hitokuchi/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        NotifyHitokuchi:
          Type: Schedule
          Properties:
            Schedule: cron(30 12 * * ? *)

一番下の行のScheduleの行が定時実行する時刻の指定。式の記述方法は以下を参照。
ルールのスケジュール式

ハンドラでもらえるイベント情報の時刻

先程のテンプレートだと、notify_hitokuchi/app.py内のlambda_handler関数が実行時に呼び出される関数になります。

app.py
def lambda_handler(event, context):

eventには辞書形式でイベント情報が入ってきます。
今回はCloudWatchイベントでキックされた時刻が欲しかったのですが、実行時刻はevent['time']にISO 8601形式で入ってきます。
それをそのまま「datetime.fromisoformat()」関数に渡せばdatetimeが入手できる…と思いきや、「2019-07-10T12:30:00Z」という形だと「datetime.fromisoformat()」は処理できないようでして…
以下のようなコードでdatetimeを入手しました。

# event['time']に'2019-07-10T12:30:00Z'形式の文字列が入ってくる
datetime_str = event['time'].replace('Z', '+00:00')

# '2019-07-10T12:30:00+00:00'形式だとfromisoformatで処理できる
datetime_utc = datetime.fromisoformat(date_str)

pip等でインストールしたライブラリ

pip等でインストールしたライブラリをAWS Lambdaで使う場合、ライブラリを1箇所にまとめてZIPで固めて…という話をよく見かけますが、samを使っている分にはsam buildで済むようです。

$ sam build
2019-07-19 07:42:34 Building resource 'NotifyHitokuchiFunction'
2019-07-19 07:42:34 Running PythonPipBuilder:ResolveDependencies
2019-07-19 07:42:44 Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Package: sam package --s3-bucket <yourbucket>

↓↓↓2019-07-20追記↓↓↓
sam buildの前にrequirements.txtをCodeUriで指定したパスに設置が必要です。
自分はpipenvを使っているので、以下でrequirements.txtを生成しました。

$ $ pipenv lock -r > notify_hitokuchi/requirements.txt 
$ cat notify_hitokuchi/requirements.txt 
-i https://pypi.org/simple
beautifulsoup4==4.7.1
bs4==0.0.1
certifi==2019.6.16
chardet==3.0.4
decorator==4.4.0
html5lib==1.0.1
idna==2.8
mojimoji==0.0.9.post0
py==1.8.0
python-dotenv==0.10.3
requests==2.22.0
retry==0.9.2
six==1.12.0
soupsieve==1.9.2
urllib3==1.25.3
webencodings==0.5.1

また、上記requirements.txtに記載のあるmojimojiのようにC/C++で作られているライブラリは動作環境でコンパイルする必要がありますが、sam buildに--use-containerオプションを付けることでdockerコンテナ上でビルドが行われ、Lambda上で動くライブラリを作ることができます。素晴らしい!

$ sam build --use-container
2019-07-20 09:43:51 Starting Build inside a container
2019-07-20 09:43:51 Building resource 'NotifyHitokuchiFunction'

Fetching lambci/lambda:build-python3.7 Docker container image......
2019-07-20 09:43:55 Mounting /Users/masaminh/develop/notify_hitokuchi_aws/notify_hitokuchi as /tmp/samcli/source:ro,delegated inside runtime container

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Package: sam package --s3-bucket <yourbucket>

Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

↑↑↑2019-07-20追記↑↑↑

ローカルでの動作確認

まずハンドラに渡されるイベント情報を作っておく必要がありますが、その元はsam local generate-eventを使って取得できます。
今回はCloudWatchのイベントですが、以下で作成しました。

$ sam local generate-event cloudwatch scheduled-event > event.json
$ cat event.json
{
  "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
  "detail-type": "Scheduled Event",
  "source": "aws.events",
  "account": "",
  "time": "1970-01-01T00:00:00Z",
  "region": "us-east-1",
  "resources": [
    "arn:aws:events:us-east-1:123456789012:rule/ExampleRule"
  ],
  "detail": {}
}

実際にローカルで動作確認を行う際には、

$ sam local invoke -e event.json NotifyHitokuchiFunction
2019-07-19 08:03:10 Invoking app.lambda_handler (python3.7)
2019-07-19 08:03:10 Found credentials in shared credentials file: ~/.aws/credentials

Fetching lambci/lambda:python3.7 Docker container image......

という感じで呼び出すことができます。Dockerコンテナ上で動くので起動するのに少し時間がかかります。

デプロイ

まずパッケージを作成。

$ sam package --s3-bucket バケット名 --output-template-file package.yaml

Successfully packaged artifacts and wrote output template to file package.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /Users/masaminh/develop/notify_hitokuchi_aws/package.yaml --stack-name <YOUR STACK NAME>

引き続き、メッセージに従ってデプロイ...

$ aws cloudformation deploy --template-file package.yaml --stack-name notify-hitokuchi

Waiting for changeset to be created..

Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state Status: FAILED. Reason: Requires capabilities : [CAPABILITY_IAM]

メッセージのままだと上記のように怒られるので、capabilitiesにCAPABILITY_IAMを設定します。

$ aws cloudformation deploy --template-file package.yaml --stack-name notify-hitokuchi --capabilities CAPABILITY_IAM

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - notify-hitokuchi

デプロイできました。

ログ出しに関して

app.py
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    logger.info('start: event.time=%s', event['time'])

こんな感じで普通にloggerから出力すれば、CloudWatchで見られます。

[INFO] 2019-07-18T12:30:28.336Z 51865e95-cacd-4f9d-80e9-6e1f148357b9 start: event.time=2019-07-18T12:30:00Z

おわりに

今回は競馬的なことは書かずにノウハウ的なところのみでした。(Lambda関数名からして競馬系の処理だなぁという感じではありますが・笑)
引き続き競馬系の個人開発はやってますので、またそのうち競馬開発系の記事を書きたいな〜と思います(笑)

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

データ数が入力次元より少ない場合の重回帰分析

はじめに

この記事は古川研究室 Workout_calendar 4日目の記事です。
本記事は古川研究室の学生が学習の一環として書いたものです。

本記事では重回帰分析について述べます。
重回帰分析を扱った時にプログラムは回ったのに予測値がめちゃくちゃ!ってことはよくありますよね。その原因の一つに学習データの型(サイズ)があるかもしれません。重回帰では入力の次元数よりもデータ数が少ないと、出力値に大きな差が出ることがあります。
本記事では重回帰分析のアルゴリズムについて詳しく述べた後、入力が高次元かつデータ数が少ない場合に何が起こっているのか、またその解決法について述べます。

重回帰分析とは

問題設定

観測データとしてD次元のベクトルの入力$\mathbf x\in \mathbb R^D$と出力$y\in \mathbb R$のペア$\left\{(\mathbf{x}_n,y_n)\right\} _{n=1}^N $から$y=f(\bf{x})$となるような関数$f$を推定すること

重回帰分析を解く

$\begin{equation}
f(\mathbf{x})=w^{(1)}x^{(1)}+w^{(2)}x^{(2)}+...+w^{(D)}x^{(D)} \tag{1}
\end{equation}$
とモデルを定義します。
$\begin{equation}
f(\mathbf{x})=\sum_{d=1}^{D}{w^{(d)}}{x^{(d)}} \tag{2}
\end{equation}$
$\mathbf{w}=({w^{(1)}},{w^{(2)}},...,{w^{(D)}})^T,\mathbf{x}=(x^{(1)},x^{(2)},...x^{(D)})^T$とおくと
$\begin{equation}
f(\mathbf{x})=\mathbf{w}^T\mathbf{x} \tag{3}
\end{equation}$

観測データの出力とモデルの出力の二乗誤差の総和$J_\mathrm{LS}(\mathbf{w})$が最小となる$\mathbf{w}$を求めていきます。

\begin{align}
J_\mathrm{LS}(\mathbf{w})&=\sum_{n=1}^{N}(y_n-f(\mathbf{w}))\\
&=\sum_{n=1}^{N}(y_n-\mathbf{w}^T\mathbf{x}_n)\\
&=\sum_{n=1}^{N}(y_n-\mathbf{x}_n^T\mathbf{w})
\end{align}
\mathbf{y}=(y_1,y_2,...,y_N)^T,\mathbf{X}=\begin{pmatrix}x_1^{(1)} & x_1^{(2)} & \cdots & x_1^{(D)} \\
x_2^{(1)} & x_2^{(2)} & \cdots & x_2^{(D)} \\
\vdots & \vdots & \ddots & \vdots \\
x_N^{(1)} & x_N^{(2)} & \cdots & x_N^{(D)}&\end{pmatrix}

とおくと

\begin{eqnarray}
    J_\mathrm{LS}(\mathbf{w})&=&(\mathbf{y}-\mathbf{X}\mathbf{w})^T(\mathbf{y}-\mathbf{X}\mathbf{w})\\
    &=&(\mathbf{y}^T-\mathbf{w}^T\mathbf{X}^T)(\mathbf{y}-\mathbf{X}\mathbf{w})\\
    &=&\mathbf{y}^T\mathbf{y}-\mathbf{y}^T\mathbf{X}\mathbf{w}-\mathbf{w}^T\mathbf{X}^T\mathbf{y}+\mathbf{w}^T\mathbf{X}^T\mathbf{X}\mathbf{w}\\
    &=&\mathbf{y}^T\mathbf{y}-2\mathbf{w}^T\mathbf{X}^T\mathbf{y}+\mathbf{w}^T\mathbf{X}^T\mathbf{X}\mathbf{w}\\
\end{eqnarray}

上式を$\mathbf{w}$について偏微分し0とおくと

\begin{eqnarray}
    \frac{\partial}{\partial \mathbf{w}}J_\mathrm{LS}&=&\frac{\partial}{\partial \mathbf{w}}(\mathbf{y}^T\mathbf{y}-2\mathbf{w}^T\mathbf{X}^T\mathbf{y}+\mathbf{w}^T\mathbf{X}^T\mathbf{X}\mathbf{w})\\
    0&=&0-2\mathbf{X}^T\mathbf{y}+\{\mathbf{X}^T\mathbf{X}+(\mathbf{X}^T\mathbf{X})^T\}\mathbf{w}\\
    2\mathbf{X}^T\mathbf{X}\mathbf{w}&=&2\mathbf{X}^T\mathbf{y}\\
\mathbf{X}^T\mathbf{X}\mathbf{w}&=&\mathbf{X}^T\mathbf{y}\\
\mathbf{w}&=&(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{y}
\end{eqnarray}

となりパラメータ$\mathbf{w}$を求めることができました!

具体的な例を当てはめてみる

アイスクリーム屋の一日の売上をその日の気温と湿度より予測する重回帰問題を考えてみます。

# 必要なライブラリのimport
import numpy as np
import random
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# 入力データセット
N = 20
a1 = 1
a2 = 15
temp = np.random.uniform(22,35,N)
humi = np.random.uniform(0.0,0.9,N)
sales = a1*temp + a2*humi + np.random.rand(N)*2 - 1

# キャンバス作成
fig = plt.figure(figsize=(9, 9))
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.set_title("example data: sales of ice")
ax.set_xlabel("humidity")
ax.set_ylabel("temperature")
ax.set_zlabel("sales of ice")
ax.scatter(temp, humi, sales,s=30, c='red')

# データセットを行列に
x_1 =temp [:,None]
x_2 =humi [:,None]
y = sales
X = np.concatenate([x_1,x_2],axis=1)

# 重回帰の解
w = np.linalg.inv(X.T@X)@X.T@y
new_samples_num = 30
new_temp,new_humi=np.meshgrid(np.linspace(22,35,new_samples_num),np.linspace(0.0,0.9))
new_temp=new_temp[:,:,None]
new_humi=new_humi[:,:,None]
new_X=np.concatenate([new_temp, new_humi], axis=2)
new_y=new_X[:,:,0]*w[0]+new_X[:,:,1]*w[1]

fig=plt.figure(1)
ax=Axes3D(fig)
ax.set_xlabel('temperature')
ax.set_ylabel('humidity')
ax.set_zlabel('sales')
ax.set_title('regression by linear model')
ax.scatter(X[:,0],X[:,1],y,c='red',label='trainig data $(x_{n1},x_{n2},y_n)$')
ax.plot_wireframe(new_X[:,:,0], new_X[:,:,1], new_y, color='b',label='estimated function $f_w(x)$')
plt.legend(fontsize=12)
plt.show()

気温と湿度からその日のアイスの売上を予測することができるようになりました!

入力の次元数がデータ数よりも多い場合

以下の重回帰モデルの解を見てみると、重回帰では$\mathbf{X}^T\mathbf{X}$が逆行列を持つ、すなわち正則であることが前提としてあることが分かります。

\begin{eqnarray}
\mathbf{w}&=&(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{y}
\end{eqnarray}

ここで入力の次元数$D$がデータ数$N$より多い場合を考えてみましょう。$(D>N)$
$\mathbf{X}$を特異値分解します。

\begin{eqnarray}
\mathbf{X}=U\Sigma V^T
\end{eqnarray}

ただし$\Sigma$は$N \times D$の行列で以下に示す性質を持っています。

\begin{eqnarray}
\Sigma &=& [\Delta,\mathbf{0}] \\
\Delta &=& {\rm diag}(\sigma_1, \dots, \sigma_N)
\end{eqnarray}

このとき

\begin{eqnarray}
\mathbf{X}^T\mathbf{X}&=&(U\Sigma V^T)^TU\Sigma V^T\\
&=&V \Sigma^T U^T U \Sigma V^T\\
&=&V \Sigma^T \Sigma V^T\\
&=&V\begin{pmatrix} \Delta ^2 & \mathbf{0} \\
\mathbf{0} & \mathbf{0}
\end{pmatrix}V^T
\end{eqnarray}

となり$\mathbf{X}^T\mathbf{X}$は非正則となります。数学的には、「逆行列は求められない」で終わりなのですが、数値計算上では$\Sigma^T \Sigma$の要素が常識のように完全に$0$になるのではなく、$0$に近い非常に小さな値が入ります。それに対して無理やり逆行列を計算することになるので$\mathbf{w}$の要素にはとても大きな値が入ることになり、そのモデルから導かれた出力間には大きな差がでてしまいます。

解決法

$\mathbf{X}^T\mathbf{X}$が非正則である時にリッジ回帰という手法が使えます。(他にも過学習を防ぐ場合などにも使われます)
リッジ回帰では重みベクトル$\mathbf{w}$の大きさにペナルティを加えます。($\alpha>0$)

\begin{eqnarray}
    J_\mathrm{LS}(\mathbf{w})&=&\|\mathbf{y}-\mathbf{Xw}\|^2+\alpha\|\mathbf{w}\|^2
\end{eqnarray}

同様に$\mathbf{w}$で微分して0とおくと

\begin{eqnarray}
    \mathbf{w}=(\mathbf{X}^T\mathbf{X}+\alpha I)^{-1}\mathbf{X}^T\mathbf{y}
\end{eqnarray}

となりますね。
$\mathbf{X}$の特異値分解を考えると

\begin{eqnarray}
    \mathbf{X}&=&U\Sigma V^T\\
    \mathbf{X}^T\mathbf{X}+\alpha I &=& (U\Sigma V^T)^T U\Sigma V^T+\alpha I\\
    &=&V\Sigma^2V^T+\alpha VIV^T\\
    &=&V(\Sigma^2+\alpha I)V^T
\end{eqnarray}

となります。
$\mathbf{X}^T\mathbf{X}+\alpha I$の特異値は$\mathbf{X}$の特異値の2乗に正の値を足したものとなるため全て正となるので…

\begin{eqnarray}
    \rm{det}(\mathbf{X}^T\mathbf{X}+\alpha I)>0
\end{eqnarray}

となり、$\mathbf{X}^T\mathbf{X}+\alpha I$は正則となって逆行列を持ちます!

まとめ

重回帰分析において入力の次元数に対してデータ数が足りない場合の解決法とそれで解決される理由を示しました。
皆さんも問題が発生した際は自分が扱っている手法が何を前提としているかを考えるのもいいかもしれません。

参考文献:パターン認識と機械学習(上・下)

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

PythonでベクトルをL2正規化(normalization)する方法一覧

Pythonを使ってベクトルをL2正規化(normalization)する方法が色々あるのでまとめます。
※L2正則化(regularization)= Ridgeではありません。

L2正規化とは

ベクトル$x = (x_1,x_2,...,x_n)$に対応するL2正規化は以下のように定式化されます。

x_{L2\,norm} = \frac{x}{||x||_2}

ここで、$||x||_2$は、以下の式で求まる$x$のL2ノルムです。

||x||_2 = \sqrt{(\sum_i x_i^2)} = \sqrt{x_1^2 + x_2^2 + ... + x_n^2}

ベクトル$x$をL2正規化すると、長さが1のベクトルになります。

2次元空間で考えた場合、この操作は任意の2次元ベクトルを原点を中心とした半径1の円上へのベクトルに変換していると考えることができます。

image.png

3次元の場合は原点を中心とした半径1の球面上へのベクトルに変換します。

image.png

4次元以上はイメージが難しいですが、同じように原点を中心とする半径1の超球面上へのベクトルに変換すると考えれば良いと思います。

pythonでの計算方法

上記のようなL2正規化ですが、pythonでは色々と計算方法があるようなので以下にまとめます。
他にもあると思うので、見つけたら追記します。

直接計算する

定義に従って計算すれば良いです。

# 定義に従って計算
x = np.array([1,2,3,4,5])
x_l2_norm = sum(x**2)**0.5
x_l2_normalized = x / x_l2_norm
# 検算
np.linalg.norm(x_l2_normalized,ord=2)
# 1.0

numpy.linalg.norm

numpy.linalg.normを使って計算することも可能です。
こいつはベクトルxのL2ノルムを返すので、L2ノルムを求めた後にxを割ってあげる必要があります。

# numpy.linalg.normを使う
x = np.array([1,2,3,4,5])
x_l2_norm = np.linalg.norm(x,ord=2)
x_l2_normalized = x / x_l2_norm
# 検算
np.linalg.norm(x_l2_normalized,ord=2)
# 1.0

sklearn.preprocessing.normalize

みんな大好きscikit-learnでも計算可能です。
2次元の場合はaxisで軸の指定します。axis=0で列方向、axis=1(デフォルト)で行方向に正規化を行います。

# sklearn.preprocessing.normalizeを使う
from sklearn.preprocessing import normalize
x = [[1,2,3,4,5],]
x_l2_normalized = normalize(x,norm='l2')
x_2d = [[1,2,3,4,5],[6,7,8,9,10]]
x_2d_l2_normalized = normalize(x_2d,norm='l2')
x_2d_l2_normalized2 = normalize(x_2d,norm='l2',axis=0)
# 検算
print(np.linalg.norm(x_l2_normalized,ord=2))
print(np.linalg.norm(x_2d_l2_normalized[0,:],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[1,:],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[:,0],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[:,1],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[:,2],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[:,3],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[:,4],ord=2))
# 1.0
# 1.0
# 1.0
# 1.0
# 1.0
# 1.0
# 1.0
# 0.9999999999999999

keras.backend.l2_normalize

kerasにもあります。backendはtensorflowです。
(tf.keras.backend.l2_normalizeというものもありました)

# keras.backend.l2_normalizeを使う
from keras import backend as K
x = [1.,2.,3.,4.,5.]
x_2d = [[1.,2.,3.,4.,5.],[6.,7.,8.,9.,10.]]
x_l2_normalized = K.l2_normalize(x,axis=0)
x_2d_l2_normalized = K.l2_normalize(x_2d,axis=0)
x_2d_l2_normalized2 = K.l2_normalize(x_2d,axis=1)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    x_l2_normalized = sess.run(x_l2_normalized)
    x_2d_l2_normalized = sess.run(x_2d_l2_normalized)
    x_2d_l2_normalized2 = sess.run(x_2d_l2_normalized2)
# 検算
print(np.linalg.norm(x_l2_normalized,ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,0],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,1],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,2],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,3],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,4],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[0,:],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[1,:],ord=2))
#1.0000001
#1.0
#0.99999994
#1.0
#0.99999994
#1.0
#1.0000001
#1.0

tf.math.l2_normalize

※tensorflow 1.11まではtf.nn.l2_normalizeとして提供されていたようです。

import tensorflow as tf

x = tf.Variable([1,2,3,4,5], dtype=tf.float32)
x_2d = tf.Variable([[1,2,3,4,5],[6,7,8,9,10]], dtype=tf.float32)
x_l2_normalized = tf.math.l2_normalize(x,axis=0)
x_2d_l2_normalized = tf.math.l2_normalize(x_2d,axis=0)
x_2d_l2_normalized2 = tf.math.l2_normalize(x_2d,axis=1)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    x_l2_normalized = sess.run(x_l2_normalized)
    x_2d_l2_normalized = sess.run(x_2d_l2_normalized)
    x_2d_l2_normalized2 = sess.run(x_2d_l2_normalized2)
# 検算
print(np.linalg.norm(x_l2_normalized,ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,0],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,1],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,2],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,3],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,4],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[0,:],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[1,:],ord=2))
#1.0000001
#1.0
#0.99999994
#1.0
#0.99999994
#1.0
#1.0000001
#1.0

おわりに

いろいろありますねー。用途に合わせて使い分けていきましょー(?)
(pythonは変数のスコープどうなってんすか。。。)

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

畳み込み演算とim2col

CNNの畳み込みの計算をpythonで実装してみたらim2colになっていた話。

im2colって?

かの有名な「ゼロから作るdeep learning」の畳み込み演算に出てくる関数。(自分はこの本持ってませんが…)検索するとこんなのが出てきます。

def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
    N, C, H, W = input_data.shape
    out_h = (H + 2*pad - filter_h)//stride + 1
    out_w = (W + 2*pad - filter_w)//stride + 1

    img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
    col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))

    for y in range(filter_h):
        y_max = y + stride*out_h
        for x in range(filter_w):
            x_max = x + stride*out_w
            col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]

    col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
    return col

img2colでも引っ掛かりますが、多分同じ内容です。

単純な畳み込み計算

単純にfor文で畳み込みを計算してみます。
入力$(224,224,8)$を出力$(224,224,16)$に変換するサイズ$(3,3)$の二次元畳み込みを考えるとします。
畳み込みをforループの力業で無理に計算しようとすれば以下のような計算をすればよいことになります。

import numpy as np
import itertools

np.random.seed(100)

#(224,224,8)=>(224,224,16)
input = np.ones((224,224,8))
output = np.zeros((224,224,16))
weight = np.random.random_sample((3,3,8,16))

input = np.pad(input, [(1,1),(1,1),(0,0)], 'constant')  # (224,224,8)=>(226,226,8)
x = np.zeros((224,224,8,16))

# case simple conv cal
for ch1, ch2, i, j, k, l in itertools.product(range(8), range(16), range(224), range(224), range(3), range(3)):
    x[i,j,ch1,ch2] += input[i+k,j+l,ch1] * weight[k,l,ch1,ch2]

output = np.sum(x, axis=(2))
output1= [[[17.6167424  16.46092477 16.71833326 ... 15.7476869  16.24364858
   15.14112872]...
time= 74.62933945655823[s]

単純な式で記述出来ますが、この多重for文では計算に非常に時間がかかります。
この単純な畳み込み計算は現実的ではありません。

一次元畳み込みの場合

最初は簡単な一次元畳み込みを代わりに考えてみます。
ここで$*$は畳み込み演算子です。するとこの式は以下のように記述できます。
この整理した$z_i$の要素はZeroPaddingした$(0,x_1,x_2,...,x_{n},0)$からひとつずつずらしながら取り出す長さ$n$の配列になります。
このようにZeroPaddingした配列をずらして得た配列をあらかじめ用意してやり、これに畳み込みfilter要素を行列計算してやると多重for文よりも高速になります。

\begin{align}
y &= A*x \\
y &= (A_1,A_2,A_3) * (x_1, x_2, x_3, ... x_m, ... ,x_n)\\
&=(A_2x_1+A_3x_2, A_1x_1+A_2x_2+A_3x_3, ... \\
&\qquad A_1x_{m-1}+A_2x_{m}+A_3x_{m+1},...\\
&\qquad A_1x_{n-1}+A_2x_{n} )\\
&=A_1(0,x_1,x_2,...,x_{n-2},x_{n-1})\\
&+A_2(x_1,x_2,x_3...,x_{n-1},x_{n})\\
&+A_3(x_2,x_3,x_4...x_{n},0)\\
&=(A_1,A_2,A_3)\cdot(z_1,z_2,z_3)
\end{align}

二次元の畳み込みの場合もこれと同様に考える事ができます。

実装

以下はZeroPaddingしたinputをずらしてxの配列を取り、それを変形して重みとの行列積計算を行います。ここでfilter要素数9×入力チャンネル数8=内積計算の長さ$72$になってます。
念のため範囲を全部書けば$(224,224)$をZeroPaddingした$(226,226)$の配列の内$(0:224,0:224),$$(1:225,0:224),$$(2:226,0:224),$$(0:224,1:225),$$(1:225,1:225),$$(2:226,1:225),$$(0:224,2:226),$$(1:225,2:226)$$,(2:226,2:226)$の9通りの範囲になります。

input = np.pad(input, [(1,1),(1,1),(0,0)], 'constant')  # (224,224,8)=>(226,226,8)

# case im2col
x = np.zeros((224,224,8,3,3))
weight = weight.transpose((2,0,1,3))               # (3,3,8,16) => (8,3,3,16)
weight = np.reshape(weight,(72,16))                # (8,3,3,16) => (72,16)

for i, j in itertools.product(range(3), range(3)):
    x[:,:,:,i,j] = input[i:224+i,j:224+j]

x = np.reshape(x,(224*224,72))                     # (224,224,8,3,3) => (224*224,72)
x = np.dot(x,weight)                               # (224*224,72)*(72,16) => (224*224,16)
output = np.reshape(x,(224,224,16))                # (224*224,16)    => (224,224,16)
output2= [[[17.6167424  16.46092477 16.71833326 ... 15.7476869  16.24364858
   15.14112872]...
time= 0.0625007152557373[s]

この場合、計算時間はかなり短くなりました。また、計算結果も単純な畳み込み計算結果と合ってます。
ここでこの内容を探してみると前述のim2col関数が引っ掛かりました。
比較してみるとim2col関数にはSample数の次元がありますが、やってることはほとんど同じです。
image.png

結局、畳み込み演算も単なる行列積演算なんですね。

備考:

1. 畳み込みフィルターの大きさが(5,5)の場合

input = np.pad(input, [(2,2),(2,2),(0,0)], 'constant')  # (224,224,8)=>(228,228,8)
x = np.zeros((224,224,8,5,5))

for i, j in itertools.product(range(5), range(5)):
    x[:,:,:,i,j] = input[i:224+i,j:224+j]

2. stridesの大きさが(1,2)の場合

strides = (1,2)
input = np.pad(input, [(1,1),(1,1),(0,0)], 'constant')  # (224,224,8)=>(226,226,8)

for i, j in itertools.product(range(3), range(3)):
    x[:,:,:,i,j] = input[i:224+i:strides[0],j:224+j:strides[1]]

3. dilated convolutionの場合

input = np.pad(input, [(2,2),(2,2),(0,0)], 'constant')  # (224,224,8)=>(228,228,8)

for i, j in itertools.product(range(3), range(3)):
    x[:,:,:,i,j] = input[i*2:224+i*2,j*2:224+j*2]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

タブローで地図情報を扱う時

目的

BigQueryにある緯度経度情報をTableauで可視化したい

問題

緯度経度情報(GEO系)をBigqueryでPOINTとかに変換してもTableauで文字列としてしか認識してくれない

解決方法

・python でshapeファイルに変換(geojsonとかでもなんでもいいと思う)

サンプルコード

import csv
import shapefile

w = shapefile.Writer("../data/point", shapeType=shapefile.POINT)

w.field("id", "C", "40")
w.field("date", "C", "40")
w.field("price", "C", "40")
w.field("flg", "C", "40")

with open("../data/___.csv") as text:
    reader = csv.DictReader(text)
    for row in reader:
        w.record(str(row['id']), str(row['date']), str(row['price']), str(row['flg']))
        w.point(float(row['longitude']), float(row['latitude']))

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

Toioで遊んでみる (2)

前回はToioのキューブに対する基本的な操作まで触れたので、今回はそれらを組み合わせて「キューブを好きな座標に移動する」といったナビゲーション処理について書いてみる。

ナビゲーション処理

このへん: https://github.com/tomoto/tomotoio/blob/master/tomotoio/navigator.py

キューブの底面にはカメラが付いており、マットやシールに印刷された特殊パターンを読み取って、「今マット上のどの座標にどの角度でいる」とか「今どのシールの上に乗っている」といった情報(仕様書の用語だとToio ID)がわかる仕組みになっている。こういった位置情報に基づき、キューブが目的の位置なり姿勢なりに到達するようモーターにフィードバック制御をかけるのがこのナビゲーション処理である。

このナビゲーション処理は、位置情報のNotificationストリームに駆動される形で実装されている。つまり、Notificationのコールバックの中で次のモーターの状態を決定して指示を出す。そのため、Notificationストリームがスムーズに流れない環境であれば意図しない動作をする可能性がある。また、タイミングの変化を相殺する仕組みもあまり入れていないため、ストリームの流れる速度が変われば(例えば20イベント/秒で流れる環境と5イベント/秒で流れる環境とでは)加減速カーブなどが変わってしまう可能性がある。結局のところ、自分のRaspberry Pi 4に合わせてロジックやパラメタを調整しているので、環境が変わればそれなりの対応が必要かもしれないことに注意して欲しい。

指定された方向への回転(rotate)

このへん: https://github.com/tomoto/tomotoio/blob/master/tomotoio/navigator.py#L57

rotate.py でデモしている動きである。ナビゲーションの中では最も簡単。なるべく迅速に方向が決まるよう、現在の角速度、目標角度までの残り角度などを用いて、スムーズにモーターの速度を上げ下げする工夫をしているくらい。目標精度±3度くらいシャキッと余裕で決まる。

指定された座標への移動(move)

このへん: https://github.com/tomoto/tomotoio/blob/master/tomotoio/navigator.py#L95

symmetric.py でデモしている動きである。一見簡単そうだが見た目ほど自明ではなく、様々なアプローチが考えられる類のもの。変にカクカクした動きや無駄な大回りを避けるため、まず目標座標が自分のおよそ前方(デフォルトで±30度以内)になければ前節のrotateナビゲーションを行い、およそ前方にあればそこに至る円弧の曲率を計算して左右のモーターの速度を設定する、という方式にした。目標地点でちゃんと止まれるようにスムーズな速度の上げ下げを行うのは前節と同じ。将来的には経由点を指定しできるようにして、それらを滑らかに踏んでいくような処理にしたい。

指定された円上の周回運動(circle)

このへん: https://github.com/tomoto/tomotoio/blob/master/tomotoio/navigator.py#L149

circle.py でデモしている動きである。円の中心座標と半径を指定すると、その円上をキューブが周回運動する。実装的には、目標円上の自分に一番近い点から接線方向ちょっと先の座標を目標として、前節のmoveナビゲーションをかけるというふうになっている(したがい実際の移動軌跡は目標円より若干外側になると考えられる)。目標座標は常に接線方向ちょっと先に更新されるので、「人参をぶらさげた馬」のように決して止まることはない。回転方向はデフォルトで時計回りで、マットからはみ出しそうになると回転方向を反転する。

目標パラメタの動的な変更

上記3種類のナビゲーションすべて、目標角度・目標座標・目標円のパラメタを動的に変えることができ(updateTarget())、キューブはそれに追随して動く。

モーターの時限制御

モーターを回すときには1秒ちょっとの時間制限をかけておくのがよい。理論的には無限時間でもいいのだが、マットから落ちたりしてNotificationストリームが止まった時にモーターが延々と回り続けるのは具合が悪い。

マット情報

位置情報から得られる座標は仕様書のここに書かれているようにマットによって値の範囲が異なる。(ちなみに工作生物ゲズンロイドのマットは認識してくれない。何らかの隠しオプションを設定する必要があるのだろう。)

Navigatorには自分がどのマット上にいるかの情報が乗っており、マット上の相対位置取得やはみ出し防止処理を書けるようになっているが、現時点ではトイオ・コレクションマットの表側に決め打ちしてある。(See https://github.com/tomoto/tomotoio/blob/master/tomotoio/navigator.py#L180)

一人サッカー

最初に紹介した一人サッカーについて軽く紹介してみよう。
コードはこれ: https://github.com/tomoto/tomotoio/blob/master/examples/soccer.py

ボールの動き

このへん: https://github.com/tomoto/tomotoio/blob/master/examples/soccer.py#L20

ボールは独立・自律的である。つまり、プレイヤーなど周囲の環境に依存することなく、ちょんと押されたらその方向にしばらく動いて止まるだけである。これはカスタムナビゲーションロジックとして実装し、Navigator.setCommand()でボールのNavigatorに設定してしまうのが楽である。こうすればNotificationスレッドの側で勝手に処理されるので、あとは忘れておいてよい。

「ちょんと押された」の判定はなかなか難しい。感度は高い方がよく、しかしノイズを拾って勝手に動き出さない程度にしたいのだが、結局ぎこちない反応になってしまった。

プレイヤーの動き

このへん: https://github.com/tomoto/tomotoio/blob/master/examples/soccer.py#L84

ボールとは異なり、メインループの方で実装している。ナビゲーションとして実装することもできるが、まあ好みである。

もう一台キューブがあれば、互いに相手のゴールにボールを蹴り込む自律ゲームが作れるんだけどなー。

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

Pythonではじめる機械学習 KNeighborsによる回帰など

はじめに

教師あり機械学習を掘り下げていく

機械学習の種類

機械学習はクラス分類と回帰の2つに分類することができる。

クラス分類
クラス分類は前回行ったアイリスの品種判別であり,あらかじめ与えられた選択肢の中からクラスラベルを予測するものである。このクラス分類もまた2つに分けられており,2クラス分類と多クラス分類である。多クラス分類はアイリスのような例である。2クラス分類は特殊なケースで,答えがYes or Noとなるものである。例としては,メールがスパムであるかどうかなどが挙げられる。

回帰
回帰は連続値の予測を目的としたものである。例えば,ある人の年収を学歴,年齢,住所などから予測する問題などが考えられる。

forgeデータセット

クラス分類
教師あり機械学習の例としてforgeデータセットを見てみる。forgeセットは2つの特徴量を持っており,2クラス分類の例である。
下記のコードでは第1の特徴量をx軸に,第2の特徴量をy軸に取っており,Jupyter notebookに散布図を表示する。

In[1]:
%matplotlib inline
import matplotlib.pyplot as plt
import mglearn
import warnings  # Jupyter notebookの警告文を非表示にする
warnings.filterwarnings('ignore')
X, y = mglearn.datasets.make_forge()  # データセットの作成
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)  # データセットをプロット
plt.legend(["Class 0", "Class 1"], loc=4)  # 凡例(図の右下)の位置や内容を設定する
plt.xlabel("First feature")
plt.ylabel("Second feature")
print("X.shape: {}".format(X.shape))

Out[1]:
X.shape: (26, 2)  # 2つの特徴量を持つ26つのデータポイントを意味する


a308add6a8928fbe018783bc3e83441d.png

回帰
回帰アルゴリズムの例として,ここではwaveデータセットを用いる。waveデータセットは1つの特徴量(入力)とモデルの対象となる連続値のターゲット変数を持つ。下記のコードでは特徴量をx軸に,回帰のターゲット(出力)をy軸に取っており,Jupyter notebookに散布図を表示する

In[2]:
X, y = mglearn.datasets.make_wave(n_samples=40)  # 40のサンプル数
plt.plot(X, y, 'o')
plt.ylim(-3, 3)  # 座標の範囲を設定
plt.xlabel("Feature")
plt.ylabel("Target")

Out[2]:
Text(0, 0.5, 'Target')


13c83569aff2185ddc28afea9c616809.png

cancerデータセット

次はウィスコンシン乳癌データセット(cancer)を見ていく。これは癌の腫瘍を計測したもので,それぞれの癌に良性(benign)・悪性(malignant)のラベルが付けられている。ここでは新たな腫瘍が良性か悪性かを判別するモデルの実装を行う。
このデータセットはscikit-learnのload_breast_cancer関数でロードすることができる。

In[3]:
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
print("cancer.keys(): \n{}".format(cancer.keys()))

Out[3]:
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names', 'file
name'])


In[4]:
print("Shape of cancer data: {}".format(cancer.data.shape))  # cancerデータセットの特徴量とサンプル数を取得

Out[4]:
Shape of cancer data: (569, 30)  # 569のサンプル数と30の特徴量


In[5]:
import numpy as np
print("Sample counts per class:\n{}".format({n: v for n, v in zip(cancer.target_names, np.bincount(cancer.target))}))  # # 辞書内包表記でzip関数を用いている bincountではcancer.target内の0と1(benign,malignant)の個数を数えている

Out[5]:
Sample counts per class:
{'malignant': 212, 'benign': 357}


In[6]:
print("Feature names:\n{}".format(cancer.feature_names))  # 特徴量を取得

Out[6]:
Feature names:
['mean radius' 'mean texture' 'mean perimeter' 'mean area'
 'mean smoothness' 'mean compactness' 'mean concavity'
 'mean concave points' 'mean symmetry' 'mean fractal dimension'
 'radius error' 'texture error' 'perimeter error' 'area error'
 'smoothness error' 'compactness error' 'concavity error'
 'concave points error' 'symmetry error' 'fractal dimension error'
 'worst radius' 'worst texture' 'worst perimeter' 'worst area'
 'worst smoothness' 'worst compactness' 'worst concavity'
 'worst concave points' 'worst symmetry' 'worst fractal dimension']

boston_housingデータセット

In[7]:
from sklearn.datasets import load_boston
boston = load_boston()
print("Data shape: {}".format(boston.data.shape))  # bostonの特徴量とサンプル数を取得する

Out[7]:
Data shape: (506, 13)  # 506のサンプル数と13の特徴量



In[8]:
X, y = mglearn.datasets.load_extended_boston()  # 特徴量が104つに拡張されたデータセットを読み込む
print("X.shape: {}".format(X.shape))

Out[8]:
X.shape: (506, 104)  # 504のサンプル数と104の特徴量

上記のデータセットを用いて機械学習のアルゴリズムを見ていく。

K-最近傍法

In[9]:
mglearn.plots.plot_knn_classification(n_neighbors=1)  # 訓練データのうちで予測したいデータポイントに最も近い1点を示す


In[10]:
mglearn.plots.plot_knn_classification(n_neighbors=3)  # 訓練データのうちで予測したいデータポイントに最も近い3点を示す


Out[9]:
86c0ef9b4f3faae9ee32ba0a3dacf5fc.png
Out[10]:
c981db774f3a862b8ddaa1e6c9391d15.png

上記の例では2クラス分類の問題であるが,2クラス分類だけでなく任意のクラス数の場合でも予測が可能である。

scikit-learnを用いたK-最近傍点の予測

In[11]:
from sklearn.model_selection import train_test_split
X, y = mglearn.datasets.make_forge()
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)


In[12]:
from sklearn.neighbors import KNeighborsClassifier
clf = KNeighborsClassifier(n_neighbors=3)  # 近傍点の数を3としている


In[13]:
clf.fit(X_train, y_train)


In[14]:
print("Test set predictions: {}".format(clf.predict(X_test)))  # テストセットへ対して,訓練セット内の近傍点を計算し最も多いクラスを予測したものを取得する

Out[14]:
Test set predictions: [1 0 1 0 1 0 0]


In[15]:
print("Test set accuracy: {:.2f}".format(clf.score(X_test, y_test)))

Out[15]:
Test set accuracy: 0.86  # テストサンプルのうち86%に対して正しいクラスを予測した

KNeighborsClassifierの解析

平面を,そこに点があったら分類されていたであろうクラスにしたがって色をつける。こうすることでクラス0とクラス1に割り当てた場合の決定境界が見える。

In[16]:
fig, axes = plt.subplots(1, 3, figsize=(10, 3))  # plt.subplotsでは図を1x3の3つに分割して,figsizeで図のサイズを決めている
for n_neighbors, ax in zip([1, 3, 9], axes):  #近傍点の数を指定する
    clf = KNeighborsClassifier(n_neighbors=n_neighbors).fit(X, y)  # インスタンスを生成する
    mglearn.plots.plot_2d_separator(clf, X, fill=True, eps=0.5, ax=ax, alpha=.4)  # 境界線を2に分割する
    mglearn.discrete_scatter(X[:, 0], X[:, 1], y, ax=ax)  # Xの値,yの値,各データのラベルを指定
    ax.set_title("{} neighbor(s)".format(n_neighbors))  # 散布図のタイトルを設定する
    ax.set_xlabel("feature 0")  # Xのラベルを設定する
    ax.set_ylabel("feature 1")  # yのラベルを設定する
axes[0].legend(loc=3)  # 凡例の位置を設定する

Out[16]:
016082a35141c8370afcee395d7e3b84.png

次に近傍点を増やすことによるモデルの複雑さと汎化性能の関係を調べる。ここではcancerデータセットを用いる。

In[17]:
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, stratify=cancer.target, random_state=66)
training_accuracy = []
test_accuracy = []
neighbors_settings = range(1, 11)  # n_neighborsを1から11まで試す

for n_neighbors in neighbors_settings:
    clf = KNeighborsClassifier(n_neighbors=n_neighbors)  # モデルを構築
    clf.fit(X_train, y_train)
    training_accuracy.append(clf.score(X_train, y_train))  # 訓練セット精度を記録
    test_accuracy.append(clf.score(X_test, y_test))  # 汎化精度を記録

plt.plot(neighbors_settings, training_accuracy, label="training_accuracy")
plt.plot(neighbors_settings, test_accuracy, label="test_accuracy")
plt.ylabel("Accuracy")
plt.xlabel("n_neighbors")
plt.legend()

Out[17]:
e28ff64c273b611eaa6ceb3fe290021d.png
上記のグラフではy軸は精度を,x軸では近傍点の数を示している。グラフの1番左の近傍点が1つの時(過剰適合の時)には訓練セットに対する予測は完璧である。逆にグラフの1番左の近傍点が10つの時(適合不足の時)ではモデルがシンプルになりすぎて訓練セットに対する予測の精度は低くなっている。一方,テストセットに対する位予測で,近傍点が1つの場合と10つの場合では,近傍点が1つの場合の方が予測精度が低くなっている。これはモデルが複雑化されているためである。予測精度が1番低い場合でも 88%程度の精度はあるため,モデルとしてはこれで十分である。

K-近傍回帰

K-最近傍法では,回帰を行うことも可能である。この例を近傍点数が1つのものから見ていく。ここではwaveデータセットを用いる。

In[18]:
mglearn.plots.plot_knn_regression(n_neighbors=1)

Out[18]:
c10908492f79131c198d972003faaa98.png
緑の点がテストデータポイントであり,青い点が予測されたデータポイントである。次に近傍点が3つの場合を見ていく。

In[19]:
mglearn.plots.plot_knn_regression(n_neighbors=3)

Out[19]:
cab89cfffe350bcb4e93b3c7bfc43d77.png
このように複数の近傍点を用いた予測を行うことも可能である。

K-近傍回帰はscikit-learnのKNeighborsRegressorクラスに実装されており,KNeighborsClassifierと同様に利用できる。

In[20]:
from sklearn.neighbors import KNeighborsRegressor

X, y = mglearn.datasets.make_wave(n_samples=40)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)  # waveデータセットを訓練セットとテストセットに分類
reg = KNeighborsRegressor(n_neighbors=3)  # 3つの近傍点を考慮したモデルのインスタンスを作成
reg.fit(X_train, y_train)  # 訓練データと訓練ターゲットを用いてモデルを学習させる
# ↑これでテストセットに対して予測を行うことができる。


In[21]:
print("Test set predictions:\n{}".format(reg.predict(X_test)))

Out[21]:
Test set predictions:
[-0.05396539  0.35686046  1.13671923 -1.89415682 -1.13881398 -1.63113382
  0.35686046  0.91241374 -0.44680446 -1.13881398]

scoreメソッドを用いてモデル評価を行うことも可能である。scoreメソッドに対して回帰予測器はR^2スコア(決定係数)を返す。R^2スコアは回帰モデルの予測の精度を測る指数であり,0から1までの値をとる。1は完全な予測であり,0は訓練セットのレスポンス値(y_train)の平均値を返すだけのものである。
それではR^2を用いたモデルの評価を見ていく。

In[22]:
print("Test set R^2: {:.2f}".format(reg.score(X_test, y_test)))

Out[22]:
Test set R^2: 0.83

ここでのスコアは0.83となっているため比較的よいモデルであるということがわかる。

KNeighborsRegressorの解析

1次元のデータセットに対して,全てのデータポイントに対する予測を見ていく。

In[23]:
for n_neighbors, ax in zip([1, 3, 9], axes):  # 近傍点の数を指定する 1, 3, 9の近傍点で予測
    reg = KNeighborsRegressor(n_neighbors=n_neighbors)  # インスタンスを生成する
    reg.fit(X_train, y_train)  # 訓練データと訓練ターゲットを用いてモデルを学習させる
    ax.plot(line, reg.predict(line))
    ax.plot(X_train, y_train, '^', c=mglearn.cm2(0), markersize=8)  # マーカーの形,色,大きさを指定
    ax.plot(X_test, y_test, 'v', c=mglearn.cm2(1), markersize=8)  # マーカーの形,色,大きさを指定
    ax.set_title("{} neighbor(s)\n train score: {:.2f}".format(
                     n_neighbors, reg.score(X_train, y_train),  # 訓練セットの評価を行う
                     reg.score(X_test, y_test)))  # テストセットの評価を行う
    ax.set_xlabel("Feature")  # ラベル名の指定
    ax.set_ylabel("Target")  # ラベル名の指定
axes[0].legend(["Model predictions", "Training data/target", "Test data/target"], loc="best")  # 凡例の指定

Out[23]:
caff21dc8a46b49717a3c6d7c0b54ff5.png
上記のグラフでは近傍点の個数が1つの時,予測は全て訓練データポイントを通っている。そのため予測は不安定なものとなっている。考慮する近傍点の個数を増やしていくと予測はスムーズになっていくが,訓練データに対する適合度は下がる。

おわりに

長くなるのでここで一旦区切る。
次は線形モデルの回帰から行う。

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

anacondaにpolyglotをインストールする

背景

  • pipだとビルド周りで失敗したのでanacondaを使用
  • コンフリクトを避けるためpipは使わずcondaだけでパッケージ管理
  • polyglotをインストールしたい
  • pip使うインストール方法しかヒットしなかったので書いた

実行環境

  • CentOS 6.7
  • (pyenv)anaconda3-5.3.1
  • conda createで仮想環境を作ってその中でインストール

インストール作業

polyglotが使うライブラリをインストール
$ conda install numpy -y
$ conda install -c conda-forge icu -y
$ conda install -c conda-forge pyicu -y
pyicuはpython3.6をピンポイントで要求する

ここからはPyPIのパッケージからcondaパッケージを作ってインストールする
$ conda skeleton pypi morfessor
$ conda build morfessor
$ conda install --use-local morfessor -y

$ conda skeleton pypi --python-version 3.6 pycld2
pyicuがpython3.6を要求するので明示的にpython3.6でビルドしておく
$ conda build --python 3.6 pycld2
なんかすごい警告出るけど動く
$ conda install --use-local pycld2

最後にpolyglotを入れる
$ conda skeleton pypi --python-version 3.6 polyglot
これは失敗するので自分でレシピファイルを作る

$ mkdir polyglot
$ vim polyglot/meta.yaml

polyglotのレシピファイル

  • polyglot/meta.yaml を以下のように編集する
{% set name = "polyglot" %}
{% set version = "16.7.4" %}

package:
  name: "{{ name|lower }}"
  version: "{{ version }}"

source:
  url: https://files.pythonhosted.org/packages/e7/98/e24e2489114c5112b083714277204d92d372f5bbe00d5507acf40370edb9/polyglot-16.7.4.tar.gz
  sha256: f7d9cca9a212622548e9416fb89f1238b994b8860ef49e03b7c82c67f9b6269b

build:
  number: 0
  script: "{{ PYTHON }} -m pip install . --no-deps --ignore-installed -vvv "
requirements:
  host:
    - pip
    - python
  run:
    - python
    - six
test:
  imports:
    - polyglot
  • source.url にはPyPIのtar.gzファイルのパスを記述
  • source.sha256 にはtar.gzファイルのSHA256チェックサムを記述

レシピファイルが完成したらビルドしてインストール

$ conda build --python 3.6 polyglot
ビルドできたら掃除する
$ conda build purge
インストールする
$ conda install --use-local polyglot

参考

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

Pythonの基本的な書き方について

はじめに

pythonを初めにやる方が必要になりそうなことをまとめてみました。

・変数の宣言
・キーボード入力
・表示

こういったことを解説します。

変数の宣言

pythonでは、変数を宣言する際に型名は必要ありません。
型宣言をしない分、型が勝手に変わることもあるので、そこは注意が必要です。
例を以下に示します。

declear.py
a = 12
b = 13.5
s = 'Hello World!'

キーボード入力

pythonのキーボード入力は input関数を使用するだけです。
ここで1つ注意したいのは、input関数で取得した変数の型はString型であるということです。
例を以下に示します。

input.py
num = input()
#これで 変数s に入力した文字が代入されます。
#例えば、numを数値として扱いたい場合は、キャストしましょう。

num2 = int(num)
#これでキャスト終了です。

表示

pythonの表示方法も簡単で、print関数を使用するだけです。
以下に例を示します。

output.py
print(num)
print(a + b) #(こうするとa + bの値が表示されます。)

終わりに

今回は、初めてpythonを学ぶ時に最低限必要になる機能をご紹介しました。
何かに役立てば幸いです。

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

Toioで遊んでみる (1)

Toioがやってきた!

発表時から気になっていたToio (https://toio.io/ )を入手した。アメリカでは売っておらず、Amazon Japanのglobal shippingにも対応しておらず、結局国際転送サービスのお世話になった。これは絶対に楽しいに決まってるやつや!

こいつ、動くぞ!


遊んでみよう!

製品としての遊び方も丁寧に作られていて非常にハイクオリティなのだが、ここではもちろんハッカー流の遊び方の話である。

Toioのキューブは仕様書 (https://toio.github.io/toio-spec/ )が公開されていて、これに従ってBLEでキューブを制御できる。このキューブの特筆すべき点は、マット上に印刷された特殊なパターンとキューブに仕込まれたカメラにより、キューブの座標と姿勢を正確に把握できるということである。普通この手のロボットを正確な座標に向けて動かそうと思ったら、天井にカメラを付けて画像処理と組み合わせたり、かなりハードル高いのである。Toioならわりと簡単にこんな「一人サッカーロボット」とかできてしまう。

なお、キューブ操作用のライブラリは誰かが既に作っているかもしれないが、それに乗るだけでは「一番おいしいところを持ってかれる」ようなものなので自分で書く。車輪の再発明ドントコーイ(゚∀゚)!!

ソースコード

ここにどんどん放り込んでいくのでよろしく: https://github.com/tomoto/tomotoio

  • 言語: Python 3
  • OS: Linux (BLE通信にbluepyを使用しているので)
  • ハード: Raspberry Pi 4 + Raspbian Stretchを使用。Windows 10上のVMwareではBLEが不安定で意図通り動かず。
  • Python 3の仮想環境作ってpip install -e .すれば使えるはず。

キューブのスキャン

このへん: https://github.com/tomoto/tomotoio/blob/master/tomotoio/scanner.py

接続先キューブのMACアドレスはあらかじめスキャンしてファイルに書いておく。どうせ持ってるキューブは固定なので、root権限が必要なスキャン操作を毎回走らせるのはnonsenseという考え。スキャン処理自体はbluepyの基本機能だけででき、何の変哲もない。

サンプルたち

この中にある: https://github.com/tomoto/tomotoio/tree/master/examples

簡単な順に紹介:
* simple.py 接続と切断だけの最小のサンプル。
* soundeffects.py プリセットの効果音を順番に再生する。
* rotate.py キューブ#2の方向にキューブ#1を向ける。(めだま生物のキビキビ版)
* symmetric.py キューブ#2の点対称の位置にキューブ#1を移動する。
* circle.py キューブ#2を中心にキューブ#1を周回させる。
* soccer.py 上で紹介した一人サッカー。

なお utils.pyにあるのはお決まりの初期化処理。接続時にキューブに光と音で「番号、いち!に!」と言わせるのはここ。

基盤機能

キューブ操作

このへん: https://github.com/tomoto/tomotoio/blob/master/tomotoio/cube.py

インスタンスの入手は、このへん https://github.com/tomoto/tomotoio/blob/master/tomotoio/factory.py の関数にMACアドレスを指定して行う。以下のような操作ができる。

  • cube.xxx.get() 属性値(toioID, motion, button, battery)の取得
  • cube.xxx.enableNotification() Notificationの有効化(属性値が変化すると cube.xxx.addListener() で登録した関数が呼ばれる)
  • cube.setXxx() モーター、音、光などの設定
  • cube.release() 接続切断

「指定座標に移動」のような処理はこれだけではまだ難しいので、次回以降扱う。

BLE通信

このへん: https://github.com/tomoto/tomotoio/blob/master/tomotoio/blepeer.py

見ての通り、bluepyの薄いラッパ。Notificationの処理は下記のようにちょっとめんどうなのでここに隠蔽する。
1. subscribeスレッドを上げてwaitForNotifications呼び出しを回す
2. writeCharacteristicswaitForNotificationsと被らないよう、write要求はスレッド間キューをかましてsubscribeスレッドの側で処理する

BLEメッセージのエンコードとデコード

このへん: https://github.com/tomoto/tomotoio/blob/master/tomotoio/messages.py

仕様書をひたすら写経しただけ。エンコード前・デコード後のデータの入れ物は https://github.com/tomoto/tomotoio/blob/master/tomotoio/data.py で定義。

次回

指定座標への移動などのナビゲーションの話、上で紹介した一人サッカーの話でもしてみよう。

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

Python3 > datetime > カレンダの特定日に印を付ける (C++ BuilderのTCalendarのARow, ACol計算用)

動作環境
ideone (Python 3 (python 3.5)

概要

  • C++ Builderで設計中の機能をPython実装でさっと確認
  • 年を通した366の要素にあらかじめマークを付け、それをもとにカレンダの該当箇所をマーク付けする
    • C++ BuilderのTCalenderのACol, ARowの計算に使う

備考

曜日の定義は

実装

https://ideone.com/n113iw

# [x] PEP8 check

from datetime import datetime

aymd = datetime(2019, 7, 18, 12, 30, 45)


def getWeekDay(adate):
    return (adate.weekday() + 1) % 7  # for 0:Sunday[Delphi]


def getYearDay(adate):
    return (adate.timetuple().tm_yday)


def dispCal(alist):
    for idx in range(len(alist)):
        print(alist[idx], end='')
        if idx % 7 == 6:
            print()  # CRLF

# 1. preparation -------------------------
NUM_YEAR = 366  # maximum number of days in a year (including leap-year)
yrs = [[]]*NUM_YEAR
for idx in range(NUM_YEAR):
    yrs[idx] = 0
yrs[getYearDay(datetime(2019, 1, 1, 0, 0, 0))] = 1  # Jan
yrs[getYearDay(datetime(2019, 2, 1, 0, 0, 0))] = 1  # Feb.
yrs[getYearDay(datetime(2019, 3, 1, 0, 0, 0))] = 1  # Mar.
NUM_MONTH = 37  # 6 lines in calendar ( 7 * 5 lines + 2 [30, 31 days])
mts = [[]]*NUM_MONTH
for idx in range(NUM_MONTH):
    mts[idx] = 0

# 2. main part -------------------------

adt = datetime(2019, 2, 1, 0, 0, 0)  # ymdhns

awday = getWeekDay(adt)
ayday = getYearDay(adt)

for idx in range(31):  # 31: maximum for one month
    mts[awday + idx] = yrs[ayday + idx]

print('Feb')
dispCal(mts)

結果

Feb
0000010
0000000
0000000
0000000
0000010
00

備考

確認は済んだので、このコードはここまで。

さらに良いコードにする時間は今回は取らない。

関連

C++ Builderに関してはDateUtilsを使う。

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

anacondaにsentencepieceをwheelファイル経由でインストールする

背景

  • 機械学習関連のライブラリをインストールするためにanacondaを使用
  • コンフリクトを避けるためpipは使わずcondaでパッケージ管理
  • sentencepieceのPythonバインディングをインストールしたい

実行環境

  • CentOS 6.7
  • (pyenv)anaconda3-5.3.1
  • conda createで仮想環境を作ってその中でインストール

リポジトリにあるか確認

$ conda search sentencepiece

ヒットせず。

conda skeleteonを使ってみる(失敗)

  • anacondaのリポジトリになくてPyPIにある(pip installできる)パッケージは、conda skeletonを使うことでcondaにインストールするためのレシピファイルを作成できる
$ conda skeleton pypi sentencepiece

Error: No source urls found for sentencepiece
というエラーが出て失敗。

sentencepieceのPyPIにはwhlファイルはあるが
tar.gzファイルがなかったのでインストールできず失敗した模様。

自分でcondaレシピを作る

  • conda skeletonで作られるレシピを参考に作る
  • meta.yaml, build.shが必要って書いてあったけどmeta.yamlだけでインストールできた
  • sentencepiece/meta.yaml を編集する
$ mkdir sentencepiece
$ vim sentencepiece/meta.yaml
  • meta.yaml はこんな感じ
{% set name = "sentencepiece" %}
{% set version = "0.1.82" %}

package:
  name: "{{ name|lower }}"
  version: "{{ version }}"

source:
  url: https://github.com/google/sentencepiece/releases/download/v0.1.82/sentencepiece-0.1.82-cp37-cp37m-manylinux1_x86_64.whl
  sha256: 3fc02ba62f2d5c624f3f107cc256c1c5f877fb7fa80754e2e7cd7f0aa5e0c86f

build:
  number: 0
  script: "{{ PYTHON }} -m pip install sentencepiece-0.1.82-cp37-cp37m-manylinux1_x86_64.whl --no-deps --ignore-installed -vvv "
requirements:
  host:
    - pip
    - python
  run:
    - python
test:
  imports:
    - sentencepiece
  • ポイント
    • 先頭にインストール対象のライブラリ名(sentencepiece)とバージョン(0.1.82)を記述する
    • source.urlにGitHub上のwhlファイルのURLを記述する
    • sha256チェックサムはなくても動くらしいが、一応事前にダウンロードしてチェックサムを調べてから書いておく
    • build.scriptにwhlファイルをpip installするコマンドを書く(build.shの代わり)
    • --no-deps --ignore-installedを付けているのは他の依存ライブラリをインストールさせないため
    • もしかしたら必要ない行があるかも
  • できたらビルドする
$ conda build sentencepiece
最後にファイルが残ってると言われるので掃除する
$ conda build purge
  • ビルドが無事完了したらインストールする
$ conda install --use-local sentencepiece
  • インストールが完了したら作成したレシピディレクトリを削除する
$ rm -r sentencepiece

まとめ

  • conda skeleton pypiで上手くレシピが作れなくても自分でmeta.yaml書けばインストールできる
  • wheelファイルがあるライブラリなら大体condaでもインストールできそう

参考

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

xgboost で Feature Importance を算出する。

TL;DR

xgboost を用いて Feature Importanceを出力します。
object のメソッドから出すだけなので、よくご存知の方はブラウザバックしていただくことを推奨します。

この記事の内容

  • 前回の記事

    • xgboost でトレーニングデータに CSVファイルを指定したらなんか相当つまづいた。
    • Feature Importance とは、(私の理解で言えば) 説明変数ごとに出力結果に与える影響の度合いを数値化したもの。

      • 例えば、カレーを作るをときに、このカレーの味を決めているのはどの調味料の影響が大きいのか
        (ターメリックなのか?, コリアンダーなのか? みたいなこと)
        をスコアとして出力してくれるみたいな感じです。(たぶん)
  • 今回の内容

    • 前回、学習をさせるには至ったので、いよいよ Feature Importance の算出をする。

そして、もはや毎度のことなのですが、
あまり説明などが正確ではありませんことをご承知おきください。

大変遅くなりました。

記事自体はほとんどできていたのですが、
最近は研究周りの作業が忙しく、なかなか投稿する時間が取れませんでした。
すみません。

まずは、出力の仕方を知る。

例によって例のごとく、既に確立されている方法において、Feature Importance はどのように出力されているのかを確認します。

プログラム

前回も参考にさせていただいた下記のサイトにまたお世話になります。
Python: XGBoost を使ってみる のうち、「特徴量の重要度を可視化する」

xgb_fi.py
import xgboost as xgb

from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

from matplotlib import pyplot as plt

"""XGBoost で特徴量の重要度を可視化するサンプルコード"""


# sklearn にある Iris datasetを読み込む。
dataset = datasets.load_iris()
x, y = dataset.data, dataset.target

x_train, x_test, y_train, y_test = train_test_split(x, y,
                                                    test_size=0.3,
                                                    shuffle=True,
                                                    stratify=y)

# 可視化のために特徴量の名前を渡しておく
dtrain = xgb.DMatrix(x_train, label=y_train,
                        feature_names=dataset.feature_names)
dtest = xgb.DMatrix(x_test, label=y_test,
                    feature_names=dataset.feature_names)

xgb_params = {
    'objective': 'multi:softmax',
    'num_class': 3,
    'eval_metric': 'mlogloss',
}

evals = [(dtrain, 'train'), (dtest, 'eval')]
evals_result = {}

bst = xgb.train(xgb_params,
                dtrain,
                num_boost_round=100,
                early_stopping_rounds=10,
                evals=evals,
                evals_result=evals_result
                )

y_pred = bst.predict(dtest)
acc = accuracy_score(y_test, y_pred)
print("Accuracy : ", acc)


# 性能向上に寄与する度合いで重要度をプロットする
_, ax = plt.subplots(figsize=(12, 4))
xgb.plot_importance(bst,
                    ax=ax,
                    importance_type='gain',
                    show_values=False)
plt.show()

出力された表について

そうして出力されたものがこちらになります。
(n分クッキング感)
Figure_2.png

悪くない..が。棒グラフだけしかねえ。
数値でも欲しいなあ。

# よく見ると、最後の引数に show_values って書いてある。
xgb.plot_importance(bst,
                    ax=ax,
                    importance_type='gain'
                    #show_values=False
                    )

と、雑にコメントアウトして、実行。
(もちろん show_values=True と指定しても良いです。)

Figure_3.png

おおん? なんだこのきちゃない値..

この記事を参考にさせていただくと
どうやら、Feature Importance には
3種類の指標が用意されているらしいことがわかりました。

ちなみに、公式(?)ドキュメントにも同じようなことが書いてありました。

xgboost.plot_importance(booster, ax=None, ..., importance_type='weight', ...)
Plot importance based on fitted trees.
Parameters
...
importance_type (str, default "weight") –
How the importance is calculated: either “weight”, “gain”, or “cover”
”weight” is the number of times a feature appears in a tree
”gain” is the average gain of splits which use the feature
”cover” is the average coverage of splits which use the feature where coverage is defined as the number of samples affected by the split

ということで、ですね。

# この第3引数の importance_type を
xgb.plot_importance(bst,
                    ax=ax,
                    importance_type='gain'
                    #show_values=False
                    )
# このようにコメントアウトするか、
xgb.plot_importance(bst,
                    ax=ax
                    #importance_type='gain'
                    #show_values=False
                    )
# weight の指定にすると
xgb.plot_importance(bst,
                    ax=ax,
                    importance_type='weight'
                    #show_values=False
                    )

Figure_4.png

こんな風に、いい感じで表示できます。

余談ですが、

参考サイトでは「default の指定が gain になった」とのことでしたが
コメントアウトしたときと、weightで指定したときで同様の結果がでるということは、
さっきの公式っぽいDocument にあるように

importance_type (str, default "weight") –

デフォは weight なのですかね。

辞書もほしい。

それはさておき。
もう1つ欲しいものがあります。

Feature Importanceの情報を持っている辞書です。

どうしたものか.. と思い、色々雑に print してみたり、検索かけていたら
ここでいいものを見つけました。

bst.get_fscore()
{'f0': 17, 'f1': 16, 'f2': 95, 'f3': 59}

これじゃね? 早速 print します。
(入れる場所は print(Accuracy..) の下あたりに..)

print(bst.get_fscore())
{'petal length (cm)': 84, 'petal width (cm)': 42, 'sepal length (cm)': 22, 'sepal width (cm)': 19}

いい感じ! これが知りたかったのよ。

一旦まとめます。

ここまででできたプログラム

xgb_fi.py
import xgboost as xgb

from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

from matplotlib import pyplot as plt

"""XGBoost で特徴量の重要度を可視化するサンプルコード"""

# sklearn にある Iris datasetを読み込む。
dataset = datasets.load_iris()
x, y = dataset.data, dataset.target

x_train, x_test, y_train, y_test = train_test_split(x, y,
                                                    test_size=0.3,
                                                    shuffle=True,
                                                    stratify=y)

# 可視化のために特徴量の名前を渡しておく
dtrain = xgb.DMatrix(x_train, label=y_train,
                        feature_names=dataset.feature_names)
dtest = xgb.DMatrix(x_test, label=y_test,
                    feature_names=dataset.feature_names)

xgb_params = {
    'objective': 'multi:softmax',
    'num_class': 3,
    'eval_metric': 'mlogloss',
}

evals = [(dtrain, 'train'), (dtest, 'eval')]
evals_result = {}
bst = xgb.train(xgb_params,
                dtrain,
                num_boost_round=100,
                early_stopping_rounds=10,
                evals=evals,
                evals_result=evals_result
                )

y_pred = bst.predict(dtest)
acc = accuracy_score(y_test, y_pred)
print("Accuracy : ", acc)

# Feature Importance の情報を持つ辞書を出力
print(bst.get_fscore())

# 性能向上に寄与する度合いで重要度をプロットする
_, ax = plt.subplots(figsize=(12, 4))
xgb.plot_importance(bst, ax=ax)
plt.show()

これで、Feature Importance に関しては
棒グラフとそれに対応する辞書の形で取得することができるようになりました。

任意のファイル(特にCSVファイル)に対して出力できるようにしたい。

さて、欲しいものの出力の仕方はわかったのですが、前回同様、データには sklearn の datasets から読んだものを使用しています。

今後、自分で勝手なデータを持ってきて、出力したいときのために、
sklearn の datasets でなくともできるようにしたいのです。

お気付きの方も多いと思いますが

こんなコードが出てきていました。

# 可視化のために特徴量の名前を渡しておく
dtrain = xgb.DMatrix(x_train, label=y_train,
                        feature_names=dataset.feature_names)
dtest = xgb.DMatrix(x_test, label=y_test,
                    feature_names=dataset.feature_names)

=> feature_names=dataset.feature_names
ここで datasets のメソッドを用いているのですよね。
(ちなみに、dataset とは dataset = datasets.load_iris() でした。)

この中身をみてみます。

print(dataset.feature_names)
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

あ、ヘッダのリストか。いけそう(罠)

Pandas で columnを得る

失敗例

長くなってもあれなので、一気に行きます。
概ね実行スクリプトの後ろにある #の後が出力結果という見た目になります。
お急ぎの方は次の見出しまで飛ばしてください。

使用したCSVファイルは Iris dataset の
1行欠けたものをCSV形式にしてあるものになります。
(ファイル名 : Fishers_Irises_train.csv)

# read_csv
import pandas as pd

file_pass = '(pardir)/Fishers_Irises_train.csv'
iris_csv = pd.read_csv(data)

#print(iris_csv)

# この辺りは前回の記事から持ってきています..
## iloc[1:]指定しているのはヘッダを読まないため。
x = iris_csv.iloc[1:, :3]
x = x.values.astype(float)
#print('Feature data :', x)

y = iris_csv.iloc[1:, 3]
y = y.values.astype(int)
y = y -1
#print('answer :', y_pd)

拙著ですが、参考までに前回の記事です。

# カラム名をリスト型で取得する
# --- 失敗例 ---
print(iris_csv.columns)  
#  >> Index(['PetalLength', 'SepalLength', 'SepalWidth', 'Species'], dtype='object')

また objectかいな..

print(iris_csv.iloc[0])
""" 出力結果
 >> PetalLength    1.4
SepalLength    5.1
SepalWidth     3.5
Species        1.0
Name: 0, dtype: float64
    # 違うそうじゃない..
"""

print(iris_csv.iloc[0].dtype)  
# >> float64 (そうでもない)
# 考えてみた。
# # astype で型変換できるんじゃ..? (アホ)
feature_cloumns = iris_csv.columns.astype(list)
#  >> Index(['PetalLength', 'SepalLength', 'SepalWidth', 'Species'], dtype='object') (できへんで。)
# 調べたら values で 変換できるとか..
feature_cloumns = iris_csv.columns.values
print(feature_cloumns)
# ['PetalLength' 'SepalLength' 'SepalWidth' 'Species']  お。

print(type(feature_cloumns))
# <class 'numpy.ndarray'>  え。

print(type(feature_cloumns) == list)  
# False  おうふ。

やっとできた。

# pandas で読み込んだ csv data から column 情報を取得
columns = iris_csv.columns

# columns を values で (npdarrayの)リストに変換
cloumns_ndarray = columns.values

# numpy の型変換を用いて、 ndarray を python 標準の list に変換
feature_cloumns = cloumns_ndarray.tolist()

# 結果観察
print(feature_cloumns)
# >> ['PetalLength' 'SepalLength' 'SepalWidth' 'Species']  お。
print(type(feature_cloumns))
# >> <class 'list'>  お。
print(type(feature_cloumns) == list)
# >> True  っしゃあ。 (めんどくさくね??)

やっていること

結局、pandasで読み込んだ CSVのカラム名を dataframe.columns で取得し
これが objectなので、dataframe.values を用いて ndarray に変換し
ndarray.tolist() を用いて NumPy配列を Python標準のリスト型に直しています。

愚かですね。

そうして Feature Importance の出力へ。

xgb_fi.py
import numpy as np
import xgboost as xgb
import pandas as pd

from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

import matplotlib.pyplot as plt
plt.style.use('ggplot')

file_pass = '(pardir)/Fishers_Irises_train.csv'
iris_csv = pd.read_csv(file_pass)
#print(iris_csv)

## iloc[1:]指定しているのはヘッダを読まないため。
x = iris_csv.iloc[1:, :3]
x = x.values.astype(float)
#print('Feature data :', X_pd)

y = iris_csv.iloc[1:, 3]
y = y.values.astype(int)
y = y -1
#print('answer :', y)

# --- fearure columns の取得 ---

# pandas で読み込んだ csv data から column 情報を取得
columns = iris_csv.columns

# columns を values で (npdarrayの)リストに変換
cloumns_ndarray = columns.values

# numpy の型変換を用いて、 ndarray を python 標準の list に変換
feature_cloumns = cloumns_ndarray.tolist()

#print(feature_cloumns)    # ['PetalLength' 'SepalLength' 'SepalWidth' 'Species']
#print(type(feature_cloumns))
#print(type(feature_cloumns) == list)


x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, shuffle=True, stratify=y)

# 可視化のためにこの時点で特徴量の名前を渡しておく
dtrain = xgb.DMatrix(x_train, label=y_train,
                    feature_names=feature_cloumns[0:3])
dtest = xgb.DMatrix(x_test, label=y_test,
                    feature_names=feature_cloumns[0:3])
# feature_columns = ['PetalLength' 'SepalLength' 'SepalWidth' 'Species']
# 'Species' を指定しないように [0:3] ([0] ~ [2])で指定。


""" # これでもよい。('Species' を先に削っておくスタイル。)

# 正解ラベルのカラム名 'Species' を含んでしまっているので、これを popする。
feature_cloumns.pop()

dtrain = xgb.DMatrix(x_train, label=y_train,
                    feature_names=feature_cloumns)
dtest = xgb.DMatrix(x_test, label=y_test,
                    feature_names=feature_cloumns)
"""


xgb_params = {
    'objective': 'multi:softmax',
    'num_class': 3,
    'eval_metric': 'mlogloss'
}

evals = [(dtrain, 'train'), (dtest, 'eval')]
evals_result = {}

bst = xgb.train(xgb_params,
                dtrain,
                num_boost_round=100,
                early_stopping_rounds=10,
                evals=evals,
                evals_result=evals_result
                )

y_pred = bst.predict(dtest)
acc = accuracy_score(y_test, y_pred)
print("Accuracy : ", acc)

print(bst.get_fscore())

# 性能向上に寄与する度合いで重要度をプロットする
_, ax = plt.subplots(figsize=(12, 4))
xgb.plot_importance(bst, ax=ax)
plt.show()

これを実行しますと $ python xgb_fi.py

{'PetalLength': 145, 'SepalLength': 93, 'SepalWidth': 58}

このような Feature Importance の情報を持つ辞書と

Figure_5.png

それに対応した棒グラフ (スコア入り)が出力されます。

まとめ

こんな感じでややつまづきながらも、
Feature Importanceを所望のファイルに対して出力する方法を
知ることができたかなと思います。

それにしても xgboost は結構完成された(機能もりもりの、という意味の)
ライブラリであることがわかってびっくりしています。

いろんなメソッドを持たせることって、予知能力というか、先見の明というか、
ニーズを察する力が必要だなあと感じるので、すごいなあという感じですね。

今回は使い方の勉強と、「出力をすること」に重きをおいていたので、
パラメータだとか正しさだとかいう点で保証ができないのですが
そのうち、何か目的を持って使うときにここで学んだことが活きればいいなと思います。

ここまで読んでいただきありがとうございました。
それでは。

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

【Programming News】Qiitaまとめ記事 July 18, 2019 Vol.4

筆者が昨日2019/7/18(木)に気になったQiitaの記事をまとめました。昨日のまとめ記事はこちら

Java

Python

Node.js

Nuxt.js

Swift

Laravel

MySQL

Git

Visual Studio

AWS

Docker

Google Apps Script

Develop

Raspberry

UML

Go言語

R言語

awk

LaTex

Redmine

更新情報

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

Pythonインタプリタをショボーンインタプリタにする

この記事は Pythonインタプリタをショボーンインタプリタにする - narupoのブログ からの転載になります。

Pythonインタプリタをショボーンインタプリタにする

こんにちは、narupoです。

Pythonインタプリタも長いあいだ使ってると飽きるものです。
そこでインタプリタを改造して気分を変えることにしました。

インタプリタに(´・ω・`)ショボーンを表示させればインタプリタが可愛くなるかも?!
果たして本当にそうなるのでしょうか。

やってみたいと思います。

CPythonをゲットする

Pythonには色々な実装がありますが、今回改造するのは最も一般的と思われるCPythonの実装です。

Gitを使ってCPythonのプロジェクトをクローンします。

$ git clone https://github.com/python/cpython.git

あとはconfigureとやってmakeしてPythonをビルドします。
環境はWindows10のWSLを使っています。Ubuntu16.04ですね。なので生成されるPythonがpython.exeになってますね。

$ cd cpython
$ ./configure
$ make
$ python.exe

CPythonを改造する

Programs/python.cにPythonプログラムのエントリーポイントがありました。

#ifdef MS_WINDOWS
int
wmain(int argc, wchar_t **argv)
{
    return Py_Main(argc, argv);
}
#else
int
main(int argc, char **argv)
{
    return Py_BytesMain(argc, argv);
}
#endif

Windows10のWSL(Ubuntu16.04)の環境では後者のmainが呼ばれます。

Py_BytesMainModules/main.cで定義されています。

int
Py_BytesMain(int argc, char **argv)
{
    _PyArgv args = {
        .argc = argc,
        .use_bytes_argv = 1,
        .bytes_argv = argv,
        .wchar_argv = NULL};
    return pymain_main(&args);
}

pymain_mainも同ファイル内に定義されています。

static int
pymain_main(_PyArgv *args)
{
    PyStatus status = pymain_init(args);
    if (_PyStatus_IS_EXIT(status)) {
        pymain_free();
        return status.exitcode;
    }
    if (_PyStatus_EXCEPTION(status)) {
        pymain_exit_error(status);
    }

    return Py_RunMain();
}

Py_RunMainも同様です。

int
Py_RunMain(void)
{
    int exitcode = 0;

    pymain_run_python(&exitcode);

    if (Py_FinalizeEx() < 0) {
        /* Value unlikely to be confused with a non-error exit status or
           other special meaning */
        exitcode = 120;
    }

    pymain_free();

    if (_Py_UnhandledKeyboardInterrupt) {
        exitcode = exit_sigint();
    }

    return exitcode;
}

pymain_run_pythonの中身はなんじゃらほい?

static void
pymain_run_python(int *exitcode)
{
    ...
    pymain_header(config);
    ...
    else {
        *exitcode = pymain_run_stdin(config, &cf);
    }
}

pymain_headerが対話モードのバージョンとかを表示している関数です。
対話モードではpymain_run_stdinが呼ばれるようです。

先にpymain_headerを見てみます。

static void
pymain_header(const PyConfig *config)
{
    if (config->quiet) {
        return;
    }

    if (!config->verbose && (config_run_code(config) || !stdin_is_interactive(config))) {
        return;
    }

    fprintf(stderr, "Python %s on %s\n", Py_GetVersion(), Py_GetPlatform());
    if (config->site_import) {
        fprintf(stderr, "%s\n", COPYRIGHT);
    }
}

バージョンとかコピーライトを表示していますね。stderrに出力しているようです。
ここのPythonをショボーンに変えておきましょう。

    fprintf(stderr, "(´・ω・`)ショボーン %s on %s\n", Py_GetVersion(), Py_GetPlatform());

pymain_run_stdinに戻ります。

static int
pymain_run_stdin(PyConfig *config, PyCompilerFlags *cf)
{
    ...
    int run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, cf);
    ...
}

PyRun_AnyFileExFlagsを見てみましょう。
Python/pythonrun.cで定義されています。

int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    ...
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);
    ...
}

PyRun_InteractiveLoopFlagsが呼ばれています。この関数を見てみましょう。

int
PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags *flags)
{
    ...
    do {
        ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
    } while (ret != E_EOF);
    ...
}

do while文の中でstdinを監視してループしているようです。
ここがPythonの対話モードでループされている所ですね。

ループの所にショボーンを追加しときましょう。

    ...
    do {
        fprintf(stderr, ">>> (´・ω・`)ショボーン\n");
        ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
        ...
    } while (ret != E_EOF);
    ...

これでショボーンインタプリタができました。
エラー時のカワイソスも追加します。

    ...
    do {
        fprintf(stderr, ">>> (´・ω・`)ショボーン\n");
        ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
        if (ret == -1 && PyErr_Occurred()) {
            /* Prevent an endless loop after multiple consecutive MemoryErrors
             * while still allowing an interactive command to fail with a
             * MemoryError. */
            if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
                if (++nomem_count > 16) {
                    PyErr_Clear();
                    err = -1;
                    break;
                }
            } else {
                nomem_count = 0;
            }
            PyErr_Print();

            // エラー出力のあとにカワイソスを表示
            fprintf(stderr, ">>> (´・ω・`)カワイソス\n");

            flush_io();
        } else {
            nomem_count = 0;
        }
#ifdef Py_REF_DEBUG
        if (show_ref_count) {
            _PyDebug_PrintTotalRefs();
        }
#endif
    } while (ret != E_EOF);
    ...

動作風景

↓がショボーンインタプリタの動作風景です。

0.gif

おわりに

しばらくこのインタプリタを使ってみた感想ですが、ショボーンがうざかったです。
みなさんもインタプリタを改造して気分を変えてみましょう。

以上、narupoでした。

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

Pythonインタプリタをショボーンインタプリタに改造する

この記事は Pythonインタプリタをショボーンインタプリタに改造する - narupoのブログ からの転載になります。

Pythonインタプリタをショボーンインタプリタにする

こんにちは、narupoです。

Pythonインタプリタも長いあいだ使ってると飽きるものです。
そこでインタプリタを改造して気分を変えることにしました。

インタプリタに(´・ω・`)ショボーンを表示させればインタプリタが可愛くなるかも?!
果たして本当にそうなるのでしょうか。

やってみたいと思います。

CPythonをゲットする

Pythonには色々な実装がありますが、今回改造するのは最も一般的と思われるCPythonの実装です。

Gitを使ってCPythonのプロジェクトをクローンします。

$ git clone https://github.com/python/cpython.git

あとはconfigureとやってmakeしてPythonをビルドします。
環境はWindows10のWSLを使っています。Ubuntu16.04ですね。なので生成されるPythonがpython.exeになってますね。

$ cd cpython
$ ./configure
$ make
$ python.exe

CPythonを改造する

Programs/python.cにPythonプログラムのエントリーポイントがありました。

#ifdef MS_WINDOWS
int
wmain(int argc, wchar_t **argv)
{
    return Py_Main(argc, argv);
}
#else
int
main(int argc, char **argv)
{
    return Py_BytesMain(argc, argv);
}
#endif

Windows10のWSL(Ubuntu16.04)の環境では後者のmainが呼ばれます。

Py_BytesMainModules/main.cで定義されています。

int
Py_BytesMain(int argc, char **argv)
{
    _PyArgv args = {
        .argc = argc,
        .use_bytes_argv = 1,
        .bytes_argv = argv,
        .wchar_argv = NULL};
    return pymain_main(&args);
}

pymain_mainも同ファイル内に定義されています。

static int
pymain_main(_PyArgv *args)
{
    PyStatus status = pymain_init(args);
    if (_PyStatus_IS_EXIT(status)) {
        pymain_free();
        return status.exitcode;
    }
    if (_PyStatus_EXCEPTION(status)) {
        pymain_exit_error(status);
    }

    return Py_RunMain();
}

Py_RunMainも同様です。

int
Py_RunMain(void)
{
    int exitcode = 0;

    pymain_run_python(&exitcode);

    if (Py_FinalizeEx() < 0) {
        /* Value unlikely to be confused with a non-error exit status or
           other special meaning */
        exitcode = 120;
    }

    pymain_free();

    if (_Py_UnhandledKeyboardInterrupt) {
        exitcode = exit_sigint();
    }

    return exitcode;
}

pymain_run_pythonの中身はなんじゃらほい?

static void
pymain_run_python(int *exitcode)
{
    ...
    pymain_header(config);
    ...
    else {
        *exitcode = pymain_run_stdin(config, &cf);
    }
}

pymain_headerが対話モードのバージョンとかを表示している関数です。
対話モードではpymain_run_stdinが呼ばれるようです。

先にpymain_headerを見てみます。

static void
pymain_header(const PyConfig *config)
{
    if (config->quiet) {
        return;
    }

    if (!config->verbose && (config_run_code(config) || !stdin_is_interactive(config))) {
        return;
    }

    fprintf(stderr, "Python %s on %s\n", Py_GetVersion(), Py_GetPlatform());
    if (config->site_import) {
        fprintf(stderr, "%s\n", COPYRIGHT);
    }
}

バージョンとかコピーライトを表示していますね。stderrに出力しているようです。
ここのPythonをショボーンに変えておきましょう。

    fprintf(stderr, "(´・ω・`)ショボーン %s on %s\n", Py_GetVersion(), Py_GetPlatform());

pymain_run_stdinに戻ります。

static int
pymain_run_stdin(PyConfig *config, PyCompilerFlags *cf)
{
    ...
    int run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, cf);
    ...
}

PyRun_AnyFileExFlagsを見てみましょう。
Python/pythonrun.cで定義されています。

int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    ...
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);
    ...
}

PyRun_InteractiveLoopFlagsが呼ばれています。この関数を見てみましょう。

int
PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags *flags)
{
    ...
    do {
        ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
    } while (ret != E_EOF);
    ...
}

do while文の中でstdinを監視してループしているようです。
ここがPythonの対話モードでループされている所ですね。

ループの所にショボーンを追加しときましょう。

    ...
    do {
        fprintf(stderr, ">>> (´・ω・`)ショボーン\n");
        ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
        ...
    } while (ret != E_EOF);
    ...

これでショボーンインタプリタができました。
エラー時のカワイソスも追加します。

    ...
    do {
        fprintf(stderr, ">>> (´・ω・`)ショボーン\n");
        ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
        if (ret == -1 && PyErr_Occurred()) {
            /* Prevent an endless loop after multiple consecutive MemoryErrors
             * while still allowing an interactive command to fail with a
             * MemoryError. */
            if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
                if (++nomem_count > 16) {
                    PyErr_Clear();
                    err = -1;
                    break;
                }
            } else {
                nomem_count = 0;
            }
            PyErr_Print();

            // エラー出力のあとにカワイソスを表示
            fprintf(stderr, ">>> (´・ω・`)カワイソス\n");

            flush_io();
        } else {
            nomem_count = 0;
        }
#ifdef Py_REF_DEBUG
        if (show_ref_count) {
            _PyDebug_PrintTotalRefs();
        }
#endif
    } while (ret != E_EOF);
    ...

動作風景

↓がショボーンインタプリタの動作風景です。

0.gif

おわりに

しばらくこのインタプリタを使ってみた感想ですが、ショボーンがうざかったです。
みなさんもインタプリタを改造して気分を変えてみましょう。

以上、narupoでした。

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

pythonのdatetime

pythonのdatetimeについて何回も同じことを調べるため、備忘録としてまとめました。

※IDLE等の対話モードで検証しました。

必要なモジュール

import datetime
from pytz import timezone

取得

現在日時

TODAY = datetime.datetime.now()    #datetime.datetime.today() も同様の結果 
TODAY    #print(TODAY)
→ datetime.datetime(2019, 7, 19, 15, 55, 42, 566682)

単体要素

TODAY.year
→ 2019
TODAY.month
→ 7
TODAY.day
→ 19    
TODAY.hour
→ 15
TODAY.minute
→ 55
TODAY.second
→ 42
TODAY.microsecond
→ 566682

日付のみ

datetime.date.today()    #TODAY.date() も同様の結果
→ datetime.datetime(2019, 7, 19)

変更

時刻を「0時0分0秒」に変更したい

TODAY.replace(hour=0, minute=0, second=0, microsecond=0)
→ datetime.datetime(2019, 7, 19, 0, 0)

変換

日付型→文字列

TODAY.strftime("%Y年%m月%d日-%H時%M分%S秒")
→ '2019年07月19日-15時55分42秒'

文字列→日付型

DATE_STR = "2019-07-19T15:55:42Z"
datetime.datetime.strptime(DATE_STR, "%Y-%m-%dT%H:%M:%SZ")
→ datetime.datetime(2019, 7, 19, 15, 55, 42)

TimeZone

UTC

# UTCの現在時
datetime.datetime.utcnow()
→ datetime.datetime(2019, 7, 19, 7, 56, 45, 543211)

# 取得済みJSTをUTCへ変換
NOW = datetime.datetime.now()    # JST
NOW
→ datetime.datetime(2019, 7, 19, 7, 57, 02, 227882)
NOW.astimezone(timezone('UTC'))
→ datetime.datetime(2019, 7, 19, 7, 57, 02, 227882, tzinfo=<UTC>)

計算

# 今日の日時から昨日を計算
YESTERDAY = TODAY - datetime.timedelta(days=1)
YESTERDAY
→ datetime.datetime(2019, 7, 18, 15, 55, 42, 566682)

# 30分後の計算
TODAY + datetime.timedelta(minutes=30)
→ datetime.datetime(2019, 7, 19, 16, 25, 42, 566682)
# timedeltaの使える引数
[days, seconds, microseconds, milliseconds, minutes, hours, weeks]

比較

比較記号

 > >= < <= ==

TODAY > YESTERDAY
→ True
TODAY < YESTERDAY
→ False

aware VS naive

  • aware

    datetimeオブジェクトがTimeZone情報を持つオブジェクト

  • naive

    datetimeオブジェクトがTimeZone情報を持たないオブジェクト

→ aware同士、naive同士しか比較できない

以下はエラーとなる

AWARE_TIME = datetime.datetime.now().astimezone(timezone('Asia/Tokyo'))
AWARE_TIME
→ datetime.datetime(2019, 7, 19, 17, 57, 36, 45409, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)

NAIVE_TIME = datetime.datetime.now()
NAIVE_TIME
→ datetime.datetime(2019, 7, 19, 17, 57, 58, 622168)

# 比較
AWARE_TIME > NAIVE_TIME
----------------------------------------------------------------
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes

解決法

#1 naiveに統一
AWARE_TIME.replace(tzinfo=None) > NAIVE_TIME
→ False    #エラーがなく、ちゃんと比較結果が表示されている
#2 awareに統一
AWARE_TIME > NAIVE_TIME.astimezone(timezone('Asia/Tokyo'))
→ False    #エラーがなく、ちゃんと比較結果が表示されている
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

公営競技を題材にGCPを勉強する

直接的にお金が絡まないと中々やる気が出ないため、公営競技を題材にしてGCPをざっくり勉強する。
なるべくサーバレスな感じで、なるべく開発もWebベースで。
GCPの色々なサービスを使ってみることを目的にしてるので、無駄の多い構成になってしまっているかも。。

やったこと(ざっくり)

  • GKE上のAirflowでバッチ処理作成
    • GKE関連のファイル類はSourceRepositoriesで管理し、PushしたらCloudBuildでデプロイ
  • Functionsでとある公営競技の過去レースのデータをダウンロードしGCSに格納する
  • GCSからBigQueryに格納
  • BigQueryに格納したデータを元にAI Platformでモデル作成
  • Functionsでスクレイピングしたデータと上記モデルからオンライン予測し、結果をFireStoreに投入
  • Functionsで予測した結果を購入

開発環境

多分これだけしか使ってない
マシン:Chromebook(ASUS Chromebook Flip C101PA)
ツール:GCP、Cloud Shell、Google Colaboratory

最初に構築した構成イメージ

image.png

image.png

①バッチ処理基盤

このあたりは一番しんどかったので、以下記事にて少し整理
https://qiita.com/yakamazu/items/0dbf643c5e8e0b65e520
https://qiita.com/yakamazu/items/aff066b0ae811dd61f02

②バッチ処理

PubSub

バッチ日付をパブリッシュする

Cloud Functions

バッチ日付のパブリッシュをトリガーに、該当日付の結果ファイルをダウンロード、解凍、整形
ファイルをダウンロードするときは/tmpディレクトリにダウンロードするのが注意点
https://cloud.google.com/functions/docs/concepts/exec

Stackdriver Monitoring

CloudFunctionsのエラーをFunction名を指定してアラート出すことができる
参考URL

Cloud Storage

ダウンロードファイルと整形後のファイルをひたすら蓄積

BigQuery

GCSのファイルをロードし、サマリテーブルの作成

③予測モデル作成とWebスクレイピング

AI Platform

XGBoostがAI Platformで使えるようだったので、cloudshellからモデル作成ジョブをキック
モデルはGCSに保存する
その後オンライン予測用にモデルをデプロイする
やり方は公式ドキュメント見たら何とかなった

ちなみに機械学習素人の個人的感覚だと、XGBoostはパラメータチューニングなしでもそれなりの精度になってる雰囲気がある。

少しハマったのは、BigQueryからPandasにデータ投入するときに、
google-cloudパッケージを使用すると「'QueryJob' object has no attribute 'to_dataframe'」が発生
 ⇒setup.py 作ってpandas-gbqインストール、使用する方法で解決
  参考

Cloud Scheduler

cronのサービス
決まった時間にPubSubメッセージのパブリッシュができる(HTTPリクエストも送れる)

Cloud Functions(Python)

当日の情報をスクレイピング(Beautiful Soup)で取得
取得した情報を元にAI Platformのオンライン予測を使って順位を予測
一定の期待値を超えてたらPubSubに購入情報をパブリッシュ

Cloud Functions(Node.js)

購入情報のパブリッシュをトリガーに起動
Puppeteerというライブラリを使用してヘッドレスブラウザ操作で対象を購入する

Firestore

当日のスクレイピングした情報や、購入情報を格納する
NoSQL(ドキュメント指向データベース)と呼ばれるもので、RDBと違って格納するデータのレイアウトが途中で変わったりしても問題なし
BigQueryへのImportもすんなりできる

この構成はお金がかかる。。

個人でやる際に、この構成で大きくお金がかかる部分は主に以下

  • GKE
    • Airflow動かそうと思ったらそれなりスペックのインスタンスを立ち上げる必要あり
    • プリエンプティブにしても月2000円くらいはかかってしまった
  • AI Platformのオンライン予測
    • 一日に150回くらいオンライン予測すると、一日50円くらいかかる

なるべくお金がかからない感じで再構築したイメージ

image.png

image.png

①バッチ処理基盤

GKEでのAirflowをやめて、Cloud Scheduler⇒Cloud Functionsでまとめて実行に切替
個人でやる規模ならこれで十分な気がする
GKEをやめることでさらにサーバレスな感じになった
この部分の料金は今のところかかってない

②オンライン予測

AI Platformのオンライン予測機能を使わずに、
GCSからモデルをダウンロードして、それをImportして予測を実施
個人でやる規模ならこれで十分な気がする
この部分の料金は今のところかかってない

感想

ChromebookとGoogleのサービスだけで結構色んなことできそう。

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

Pythonで凸包走査実装(2D版)

Pythonで凸包走査実装

convexhull2d.png

はじめに

今回は2Dの凸包走査を実装です。
凸包走査とは与えられた点群のうち一番外側の点を取り出すアルゴリズム。。。です(という認識です)
取り出された点を結ぶと鈍角ができない多角形(凸多角形)になります。
この性質がDepthセンサーで取得したポイントクラウド処理や建築設計において使えそうだなと思い実装してみました。
その忘備録として。

グラハム走査(Graham scan)

今回使用した凸包走査アルゴリズムは実装が簡単だとされるグラハム走査というものを使用しています。
ちなみに、よく目にするもので以下のような凸包走査アルゴリズムがあります。

・Gift Wrapping
・Graham scan
・Quickhull
・Divide and conquer

などなど。参考

結構奥が深そうなアルゴリズムですが、いつもながら難しいこと抜きに実装していきます。笑

アルゴリズムの流れ

  1. x座標が最小の点pを求める。
  2. p周りの点を反時計周りで角度順に並べる。
  3. 凸部の頂点を削除。

がざっくりとした流れになります。
この中でテクニックが必要なのが3の凸部頂点削除です。
ここでは符号付き面積を求めることによって、凸部となる頂点を判断します。

符号付き面積に関しては別の機会に図示しながらまとめようと思います。

簡単に説明しますと、3点の座標値を使用して三角形の面積を計算します。
この際に絶対値を考慮しない面積がマイナスになる場合が存在してしまいます。その面積のプラス、マイナスを見ることによって、3点の並びを知ることができるというモノです。

実装

ここでは座標点をp = [x, y, z]のようなリスト構造を使用し表しています。
p_listは座標点を格納するリストです。

convex_hull関数を実行すれば、凸包を構成する座標値が返されます。
その点と点を順番につないでいけば凸包図形を描画することが可能です。

詳細は省きますが僕自身はRhinocerosという3DCADを使用して描画しました。

Convexhull.py
# coding: utf-8


def signed_area(p1, p2, p3):
    '''符号付き面積を計算 '''

    area = (((p2[0] - p1[0]) * (p3[1] - p1[1])) - ((p3[0] - p1[0]) * (p2[1] - p1[1]))) / 2
    return area


def convex_hull(p_list):
    '''グラハムの凸包走査 '''

    p_sort = []
    for i in p_list:
        p_sort.append(i[0])

    # x座標が最小のものをリストの先頭に配置。
    min_index = p_sort.index(min(p_sort))
    min_p = p_list.pop(min_index)
    p_list.insert(0, min_p)

    # 座標最小の点と他の点との角度順にソート ソートアルゴリズムに選択の余地が有る。
    p_length = len(p_list)
    count = 0
    index = 0
    while True:
        count += 1
        index += 1

        area_sign = signed_area(p_list[0], p_list[index], p_list[index + 1])

        # 符号付き面積が負ならば点を入れ替え
        if area_sign < 0:
            p_list[index], p_list[index + 1] = p_list[index + 1], p_list[index]
            count = 0

        if count == p_length - 1:
            break

        if index == p_length - 2:
            index = 0

    # 凹部の点を削除する。
    index = -1
    count = 0
    while True:
        index += 1
        count += 1

        area_sign = signed_area(p_list[index], p_list[index + 1], p_list[index + 2])
        if area_sign < 0:
            del p_list[index + 1]
            count = 0

        if count == len(p_list):
            break

        if index >= len(p_list) - 3:
            index = -1
    return p_list



おわりに

最後までお読みいただきありがとうございました。
次の機会にQuickhullで実装した3D対応の凸包走査を記事にまとめようと考えていますのでよろしくお願いいたします。

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