Cucco’s Compute Hack

コンピュータ関係の記事を書いていきます。

分割してCSVに書く、分割されたCSVを読む

1年前のプログラムの一部改良。
複数ファイルを連続して読むプログラム - Cucco’s Compute Hack

ディレクトリのパスと、ファイル名(拡張子ナシ、連番ナシ)を与えて、
決まった行数を書いたファイルを保存する、それらのファイルを開いて読む、というプログラム。

読む場合は以前から継続で、イテレータを利用できるようにした。
読むファイルをすべてリストで指定していたが、pathlibで一覧を取得している。

プログラム
import csv
from pathlib import Path
import os

from faker import Faker
import random


class split_csv_writer:
    '''
    渡された値をファイルに書き落としていく
    max_linesを超えたら、別のファイルに変更する。
    '''

    def __init__(self, dirctory_path, filename_wo_extension, header_data=None, max_lines=100000):
        self.max_lines = max_lines
        if header_data == None:
            self.max_lines = max_lines-1
        self.currnet_lines = self.max_lines+1  # 初回にOpenを実行してもらうため。

        self.currnet_file_post_index = 0
        self.filename = filename_wo_extension
        self.f = None
        self.dirctory_path = dirctory_path
        self.header_data = header_data

        self.delete_existing_files()

    def close(self):
        # デストラクタでも呼んでいる
        self.currnet_file_post_index = self.currnet_file_post_index+1
        if self.f != None:
            self.f.flush()  # 実際には、ファイルに書き込みしてくれないことがあったので追加。
            self.f.close()
        self.f = None

    def delete_existing_files(self):
        p = Path(self.dirctory_path)
        r = self.filename+"*.csv"
        for existing_file_path in p.glob(r):
            print("削除します ", existing_file_path)
            os.remove(existing_file_path)

    def __del__(self):
        self.close()

    def open(self):
        if self.f == None:
            pass
        else:
            self.close()

        real_filename = self.dirctory_path + self.filename + \
            "_{0:08d}.csv".format(self.currnet_file_post_index)

        os.makedirs(self.dirctory_path, exist_ok=True)

        self.f = open(real_filename, 'w', encoding='utf-8')
        self.csv_writer = csv.writer(
            self.f, delimiter=',', quotechar='"', lineterminator='\n')
        self.currnet_lines = 0
        print("新規作成 ", real_filename)

    def writerow(self, input_data):
        '''
        input_dataには、リストかタプルでデータを与える。
        '''
        if self.currnet_lines > self.max_lines:
            self.open()
            if self.header_data != None:
                self.writerow(self.header_data)

        self.csv_writer.writerow(input_data)
        self.currnet_lines = self.currnet_lines+1


class split_csv_reader:
    def __init__(self, dirctory_path, readfilename_wo_extention, skipHeader=False):
        self.filename = readfilename_wo_extention
        self.currentFileIndex = -1
        self.fp = None
        self.csv_reader = None
        self.skipHeader = skipHeader
        self.dirctory_path = dirctory_path
        self.set_readFileNames()

    def set_readFileNames(self):
        self.readFileNames = []
        p = Path(self.dirctory_path)
        r = self.filename+"*.csv"
        for existing_file_path in p.glob(r):
            self.readFileNames.append(existing_file_path)

        # ⃣print(self.readFileNames)

    def __iter__(self):
        # next()はselfが実装してるのでそのままselfを返す
        return self

    def __next__(self):

        if self.csv_reader is None:
            # 1個目のファイルを開く
            self.nextfile()

        try:
            value = next(self.csv_reader)
            return value
        except StopIteration:
            # 終端まで来たら、このStopIterationが投げられる。
            try:
                self.nextfile()
                value = next(self.csv_reader)
                return value
            except StopIteration:
                # 次のファイルがなかったので、例外を投げてイテレータを終わる。
                # ファイルがロックされている場合もここに来るので、問題になる可能性あり。
                raise (StopIteration)
        except:
            # ここには来ない。
            raise (StopIteration)

    def nextfile(self):
        self.currentFileIndex = self.currentFileIndex + 1
        if self.fp is not None:
            self.fp.close()
        if self.currentFileIndex > len(self.readFileNames)-1:
            # 次のファイルはないので例外を投げる
            raise(StopIteration)
        else:
            self.fp = open(
                self.readFileNames[self.currentFileIndex], mode='r', newline='\n')
            self.csv_reader = csv.reader(self.fp, delimiter=',', quotechar='"')

            if self.skipHeader is True:
                next(self.csv_reader)
                # ⃣print(value)

# 動作確認用プログラム
if __name__ == "__main__":
    dirname = r"C:/lib_split_test/"  # 最後はos.sepで終わるように指定する
    filename = r"lib_split_test"

    print("#書き込みのテスト")
    headerdata = ("#", "Name", "score")
    writer = split_csv_writer(
        dirname, filename, header_data=headerdata, max_lines=3)

    fake = Faker()
    for i in range(1, 10+1):  # 最後の値は実行されないrange(1,10+1)で、1~10
        data = [i, fake.name(), random.random()]
        writer.writerow(data)
        print(data)
    writer.close()

    print("#読み込みのテスト")

    reader = split_csv_reader(dirname, filename, skipHeader=True)

    for item in reader:
        print(item)
実行結果
(base) C:\dev\ > python C:\dev\lib_sprit_csv_reader_writer.py
#書き込みのテスト
削除します  C:\lib_split_test\lib_split_test_00000000.csv
削除します  C:\lib_split_test\lib_split_test_00000001.csv
削除します  C:\lib_split_test\lib_split_test_00000002.csv
削除します  C:\lib_split_test\lib_split_test_00000003.csv
新規作成  C:/lib_split_test/lib_split_test_00000000.csv
[1, 'Carol Martinez', 0.2993282973465532]
[2, 'Monica Skinner', 0.47771227744058886]
[3, 'Stephanie Zimmerman', 0.30639552562401595]
新規作成  C:/lib_split_test/lib_split_test_00000001.csv
[4, 'Terry Williams', 0.2800113471343493]
[5, 'Scott Grant', 0.8271274374443526]
[6, 'Jose Daniels', 0.4170927481515181]
新規作成  C:/lib_split_test/lib_split_test_00000002.csv
[7, 'Adam Austin', 0.2850528257749132]
[8, 'Krista Williams', 0.9258438865231986]
[9, 'Jared Adkins', 0.6205699038539508]
新規作成  C:/lib_split_test/lib_split_test_00000003.csv
[10, 'Ryan Morales', 0.9635161039757013]
#読み込みのテスト
['1', 'Carol Martinez', '0.2993282973465532']
['2', 'Monica Skinner', '0.47771227744058886']
['3', 'Stephanie Zimmerman', '0.30639552562401595']
['4', 'Terry Williams', '0.2800113471343493']
['5', 'Scott Grant', '0.8271274374443526']
['6', 'Jose Daniels', '0.4170927481515181']
['7', 'Adam Austin', '0.2850528257749132']
['8', 'Krista Williams', '0.9258438865231986']
['9', 'Jared Adkins', '0.6205699038539508']
['10', 'Ryan Morales', '0.9635161039757013']

pythonの引数ライブラリargparse

argparseのサンプルコード。-から始まる名前にしてOptional引数にして、
action="store_true"をつけておけば、フラグとして利用できそう。

プログラム
import argparse

if __name__ == "__main__":

    parser = argparse.ArgumentParser()

    # 必須引数(=位置引数)を追加
    # type=intがない場合は文字列
    parser.add_argument(
        "square", help="display a square of a given number", type=int)
    parser.add_argument(
        "base", help="display a square of a given number", type=int)

    # Optional引数
    # 引数に -v があれば、args.verbosityはTrueになる
    parser.add_argument("-v", "--verbosity",
                        help="increase output verbosity", action="store_true")

    # Optional引数
    # とれる値の指定のある引数
    parser.add_argument("-c", "--choices", type=int,
                        choices=[0, 1, 2], help="increase output verbosity")

    args = parser.parse_args()

    # 引数の参照方法/値確認
    print(args.square**2)
    print(args.base)

    if args.verbosity:
        print("verbosity turned on ", args.verbosity)
    else:
        print("verbosity turned off ", args.verbosity)

    print("choices:", args.choices)
-h の結果
(base) c:\dev>python a00_main.py -h
usage: a00_main.py [-h] [-v] [-c {0,1,2}] square base

positional arguments:
  square                display a square of a given number
  base                  display a square of a given number

optional arguments:
  -h, --help            show this help message and exit
  -v, --verbosity       increase output verbosity
  -c {0,1,2}, --choices {0,1,2}
                        increase output verbosity
実行結果
(base) c:\dev>python a00_main.py -v -c 2 5 6
25
6
verbosity turned on  True
choices: 2

複数の2クラス分類の一括学習

タイトルでは、わけがわからないと思うが、
1種類のデータに対して、2クラス分類問題が複数ある場合の学習。
整数dに対して、2の倍数かどうかと、3の倍数かどうか、・・・を例に学習してみた。

訓練データ形式

x : 8桁のビット整数。アンダーバー2つは、xベクトルの中での次元の番号。
y0:2の倍数かどうか。
y1:3の倍数かどうか。以降5,7,11の倍数かどうか。

x__0,x__1,x__2,x__3,x__4,x__5,x__6,x__7,y0,y1,y2,y3,y4
0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,1,0,0,0,0,0
0,0,0,0,0,0,1,0,1,0,0,0,0
0,0,0,0,0,0,1,1,0,1,0,0,0
0,0,0,0,0,1,0,0,1,0,0,0,0
0,0,0,0,0,1,0,1,0,0,1,0,0
0,0,0,0,0,1,1,0,1,1,0,0,0
0,0,0,0,0,1,1,1,0,0,0,1,0
0,0,0,0,1,0,0,0,1,0,0,0,0
0,0,0,0,1,0,0,1,0,1,0,0,0
0,0,0,0,1,0,1,0,1,0,1,0,0
0,0,0,0,1,0,1,1,0,0,0,0,1

f:id:Cucco:20190530231038p:plain
学習結果
f:id:Cucco:20190530231110p:plain
ネットワーク設計
f:id:Cucco:20190530231354p:plain
学習結果

所見
  • BatchNormalization はあったほうが収束が早い。
  • ReLUは収束早い。(Sigmoidに比べて)
  • ノード数を減らして、段数を増やすより、ノード数を増やしたほうが精度が高い(今回はノイズなしだから??)
  • 分岐前のノード数は、分岐数の2倍以上あったほうが良い(?)

LEFT OUTER JOINの評価

LEFT OUTER JOINの評価

一瞬で終わる。メモリも食わない。
UNIQUE属性付けておくとさらに早い。

実施内容

データのテーブルと、クラス分け結果のテーブルのJoin。
クラス分け結果は適当に%演算で作った。

ソースコード
import sqlite3
from faker import Faker
import random
from datetime import datetime

tic0 = datetime.now()

con = sqlite3.connect(":memory:")
con.isolation_level = None  # None で自動コミットモード
cur = con.cursor()
cur.execute('PRAGMA temp_store=MEMORY;')
cur.execute('PRAGMA journal_mode=MEMORY;')
#cur.execute('PRAGMA rock_mode=OFF;')

listsize = 100000
classsize= 128

sql_create_data = "CREATE TABLE data(number INTEGER UNIQUE, name TEXT, score REAL)"
sql_create_class = "CREATE TABLE class(number INTEGER UNIQUE, label INTEGER)"
sql_instert_data = "INSERT INTO data VALUES(?,?,?)"
sql_instert_class = "INSERT INTO class VALUES(?,?)"

sql_select_join = "SELECT data.number, data.name, data.score, class.label FROM data LEFT OUTER JOIN class ON data.number = class.number"
sql_select_join_cl1 = "SELECT data.number, data.name, data.score, class.label FROM data LEFT OUTER JOIN class ON data.number = class.number WHERE class.label = 1 "
#list_data = [[None] for i in range(listsize)]
list_data = [None] * listsize
list_class = [None] * listsize
fake = Faker()


print("データ準備 開始")
for i in range(listsize):
    list_data[i] = [i, fake.name(), random.random()]
    list_class[i] = [i, i%classsize]
print("データ準備 完了")

cur.execute(sql_create_data)
cur.execute(sql_create_class)
print("テーブルの定義を表示")
table_defs = cur.execute("SELECT * FROM sqlite_master WHERE type='table'")
for item in table_defs:
    print(item)

print("データの挿入")
cur.executemany(sql_instert_data, list_data)
cur.executemany(sql_instert_class, list_class)

# ここからjoin時間計測
tic = datetime.now()

cur.execute(sql_select_join)

toc = datetime.now()

print("テーブルの中身を表示")
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())

print("classが1のテーブルの中身を表示")
cur.execute(sql_select_join_cl1)
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())

print("join {0}件 処理時間 {1}".format(listsize, toc-tic))
print("全体処理時間 {0}件 処理時間 {1}".format(listsize, datetime.now()-tic0))
con.close()
結果
(base) C:\dev\SampleCodes\hello_sqlite>python outerjoin.py
データ準備 開始
データ準備 完了
テーブルの定義を表示
('table', 'data', 'data', 2, 'CREATE TABLE data(number INTEGER UNIQUE, name TEXT, score REAL)')
('table', 'class', 'class', 4, 'CREATE TABLE class(number INTEGER UNIQUE, label INTEGER)')
データの挿入
テーブルの中身を表示
(0, 'Troy Wagner', 0.44436625795068907, 0)
(1, 'Lee Robertson', 0.8527352596455694, 1)
(2, 'Mark Franco', 0.9942993187450221, 2)
(3, 'Joel Sandoval', 0.9462360044917911, 3)
(4, 'Katherine Ho', 0.6177953257612685, 4)
(5, 'Shannon Adams', 0.6044842942129696, 5)
(6, 'Jacob Wong', 0.4513990366427033, 6)
(7, 'Howard Bennett', 0.6643356684420646, 7)
(8, 'Scott Mitchell', 0.5792325135939987, 8)
(9, 'Joshua Tyler', 0.4882390253804837, 9)
(10, 'Steven Day', 0.03463822764171387, 10)
(11, 'Heather Hughes', 0.888992343863056, 11)
(12, 'Margaret Thomas DDS', 0.402020535579534, 12)
(13, 'David Rosario', 0.8354810420435498, 13)
classが1のテーブルの中身を表示
(1, 'Lee Robertson', 0.8527352596455694, 1)
(129, 'April Pittman', 0.48442848761646606, 1)
(257, 'Mercedes Gallagher', 0.9811398290695581, 1)
(385, 'Xavier Harvey', 0.39333389385880146, 1)
(513, 'Laura Powell', 0.11691465694144565, 1)
(641, 'Mrs. Lori Gibbs MD', 0.13424141037411286, 1)
(769, 'Christina Jacobson', 0.7144345369983663, 1)
(897, 'Gregory Lowe', 0.2939644032693347, 1)
(1025, 'Michael Santos', 0.9319930754332403, 1)
(1153, 'Olivia Montgomery', 0.3756360549631732, 1)
(1281, 'Joseph Eaton', 0.7451317465818261, 1)
(1409, 'Julia Adams', 0.5858535015316725, 1)
(1537, 'George Kelley', 0.12250922691704258, 1)
(1665, 'Patricia Johnson DVM', 0.09949616494939173, 1)
join 100000件 処理時間 0:00:00
全体処理時間 100000件 処理時間 0:00:14.371519

Neural Network ConsoleのCrossEntropy損失関数(ロス関数)の使い方

Neural Network Consoleの損失関数(ロス関数)の使い方の記録。
画像処理系はSquareErrorでもいいが、カテゴリ系はそんなわけにもいかないので。

入力データ次第で、ある程度決まった形になるので、記録として残しておく。

CategoricalCrossEntropyを使う場合

ラベルは、複数値のカテゴリインデックス。1列。
affineで適当な列数に変換した後、softmax -> CategoricalCrossEntropy

f:id:Cucco:20190526165910p:plain
CategoricalCrossEntropy

BinaryCrossEntropyを使う場合

ラベルは1か0の2値。
OneHot形式の2列の場合、affineで2列に変換した後、softmax -> BinaryCrossEntropy
1列の0/1の場合、affineで1列に変換した後、sigmoid(softmaxからsigmoidに訂正) -> BinaryCrossEntropy

f:id:Cucco:20190526165832p:plain
BinaryCrossEntropy

sqlite3のexecutemanyの動作確認

1回で10万件を挿入するのに、0.35秒。
別実行した1件ソートの繰り返しでは0.42秒。大した差ではないが、ループを組まなくていいメリットあり。

値を埋めたいところは、"INSERT INTO users VALUES(?,?,?)" のようにハテナにしておく。
リストのリストや、タプルのリストにしておけば、全件Insertしてくれるようす。

プログラム
import sqlite3
from faker import Faker
import random
from datetime import datetime

con = sqlite3.connect(":memory:")
con.isolation_level = None  # None で自動コミットモード
cur = con.cursor()
cur.execute('PRAGMA temp_store=MEMORY;')
cur.execute('PRAGMA journal_mode=MEMORY;')

listsize = 100000

sql_create = "CREATE TABLE users(number INTEGER, name TEXT, score REAL)"
sql_instert = "INSERT INTO users VALUES(?,?,?)"
sql_select = "SELECT number,name,score FROM users ORDER BY number LIMIT 3"
sql_select_last = "SELECT number,name,score FROM users ORDER BY number LIMIT 3 OFFSET {0}".format(listsize-3)

list1 = [None] * listsize
fake = Faker()

print("データ準備 開始")
for i in range(listsize):
    list1[i] = [i, fake.name(), random.random()]
print("データ準備 完了")

cur.execute(sql_create)
print("テーブルの定義を表示")
tmp = cur.execute("SELECT * FROM sqlite_master WHERE type='table'").fetchall()
print(tmp)

# ここからexecutemany時間計測
tic = datetime.now()

print("データの挿入")
cur.executemany(sql_instert, list1)

# executemany時間計測終わり
toc = datetime.now()

cur.execute(sql_select)
print("テーブルの中身を表示")
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())
print()
cur.execute(sql_select_last)
print(cur.fetchone())
print(cur.fetchone())
print(cur.fetchone())

print()
print("executemany {0}件 処理時間 {1}".format(listsize, toc-tic))

con.close()
実行結果
(base) C:\dev\SampleCodes\hello_sqlite>python execmeny.py
データ準備 開始
データ準備 完了
テーブルの定義を表示
[('table', 'users', 'users', 2, 'CREATE TABLE users(number INTEGER, name TEXT, score REAL)')]
データの挿入
テーブルの中身を表示
(0, 'Dawn West', 0.5244752623689407)
(1, 'Marvin Chambers', 0.8354440852719125)
(2, 'Clinton Lowe', 0.4116042084368037)

(99997, 'Aaron Conley', 0.795391969430207)
(99998, 'Alyssa Brooks', 0.17316654138026266)
(99999, 'Marisa Peterson', 0.018786705346994004)

executemany 100000件 処理時間 0:00:00.350097