Cucco’s Compute Hack

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

Python サブモジュールのディレクトリ構造とunittestの話

プログラムとunittestを同じディレクトリに置くパタンのテスト。
パッケージ(hello_submodule)の同列にtestディレクトリを作って、unittestのファイルを置く構成が標準らしいが、パスの設定関係がとても面倒なので。

プログラムとunittestを同じディレクトリに置くパタンの場合、カレントディレクトリがhello_submoduleにあるものとしてfromなどを記述すればよさそう。

ディレクトリ構造
─hello_submodule
  │  program_main.py
  │  program_main_test.py
  │  
  ├─.vscode
  │      launch.json
  │      settings.json
  │      
  ├─sub_module_1
  │      program_submod1.py
  │      program_submod1_test.py
  │      __init__.py
  │          
  └─sub_module_2
          program_submod2.py
          program_submod2_test.py
          __init__.py
ソースコード

1. program_main.py

from sub_module_1 import program_submod1
from sub_module_2.program_submod2 import Class_Submod2

def main_func():
    x = program_submod1.func1()
    y = Class_Submod2()
    z = y.func2()

    print("this function is program_main.main_func()")
    print(f"x={x}, y={y},z={z}")

    return 3

if __name__ == '__main__':
    main_func()

2. program_main_test.py

import unittest
import program_main

class Testprogram_main(unittest.TestCase):

    def test_main_func(self):
        self.assertEqual(program_main.main_func(), 3)


if __name__ == '__main__':
    unittest.main()

3. program_submod1.py

def func1():
    print("this function is sub_module_1.func1()")
    return (1)

4. program_submod1_test.py

import unittest
from sub_module_1 import program_submod1

class Testprogram_submod1(unittest.TestCase):

    def test_func1(self):
        self.assertEqual(program_submod1.func1(), 1)

if __name__ == '__main__':
    unittest.main()

5. program_submod2.py

class Class_Submod2:
    def __init__(self):
        self.v = 2
        pass

    def func2(self):
        print("this function is sub_module_2.func2() in Class_Submod2")
        return (self.v)

6. program_submod2_test.py

import unittest
from sub_module_2 import program_submod2

class Testprogram_submod2(unittest.TestCase):

    def test_func1(self):
        x=program_submod2.Class_Submod2()
        self.assertEqual(x.func2(), 2)

if __name__ == '__main__':
    unittest.main()

7. __init__.py
空のファイル。

8. setting.json(vscodeの設定ファイル)

{
    "python.testing.unittestArgs": [
        "-v",
        "-s",
        ".",
        "-p",
        "*_test.py"
    ],
    "python.testing.pytestEnabled": false,
    "python.testing.nosetestsEnabled": false,
    "python.testing.unittestEnabled": true
}
コマンドラインでのテスト実行例
(base) C:\dev\SampleCodes\hello_submodule>python -m unittest discover -p "*_test.py"
this function is sub_module_1.func1()
this function is sub_module_2.func2() in Class_Submod2
this function is program_main.main_func()
x=1, y=<sub_module_2.program_submod2.Class_Submod2 object at 0x0000021BF07B07C8>,z=2
.this function is sub_module_1.func1()
.this function is sub_module_2.func2() in Class_Submod2
.
----------------------------------------------------------------------
Ran 3 tests in 0.005s

OK
||>

*** パスの設定関係がとても面倒な理由
unittestファイル側から、テスト対象の関数が書いてある.pyファイルをimportしないといけないが、親ディレクトリをたどってimportする設定が手間なため。
全てのunittestファイルに以下を記載してもよいが手間。lynterが問題として検出することもある。

>||
import sys
sys.path.append(r"<パッケージのディレクトリ>")

markdownで書いたドキュメントにPlantUMLの図を挿入する方法

markdownで書いたドキュメントにPlantUMLの図を挿入する方法。
以下、どちらも可能。

  1. mdファイルに直接記載する。
  2. PlantUMLのファイルを参照する。

これで、ファイル分割しつつ、図と説明をテキストファイルで記述できるようになった。

結果

f:id:Cucco:20200523173931p:plain
プレビューされる内容

ソースコード

■mdファイル

# Section1
直接のPlantUMLの記載を、図として挿入する。
```plantuml
@startuml
:Hello world;
:this is section1;
@enduml
```
# Section2

別ファイルにあるPlantUMLの記載を、図として挿入する。

```plantuml
!include a00_hello_world.pu
```
# Section3
終わり

■PlantUMLの記載ファイル(a00_hello_world.pu)

@startuml
:Hello world;
:This is on defined on
several **lines**;
@enduml

Neural Network Consoleによる学習済みニューラルネットワークの利用(その2)

識別機として作ったネットワークの中間層の値をエンコード値として使う、オートエンコーダもどきがつくりたかったのでテスト。
誤差関数には、y(OneHotにした3列のクラス値 )を入れているので、オートエンコーダではないが、実質的には同じもの

ネットワーク設計

ネットワークの設計とトレーニングはNeural Network Console(NNC)で実施。
2層では識別率が上がらなかったので、3層。
左側がエンコーダ、右側がデコーダ

f:id:Cucco:20200523124503p:plain
ネットワーク

実行結果
(base) C:\dev\SampleCodes\hello_autoenc>python network.py
2020-05-23 12:39:58,407 [nnabla][INFO]: Initializing CPU extension...
2020-05-23 12:39:59,117 [nnabla][INFO]: Parameter load (<built-in function format>): C:\dev\SampleCodes\hello_autoenc\hello_autoenc.files\20200523_112705\results.nnp
入力データ(アヤメ3種類それぞれ1個づつ)
[[4.3 3.  1.1 0.1]
 [4.9 2.4 3.3 1. ]
 [4.9 2.5 4.5 1.7]]
もともとのネットワークの計算結果(エンコード+デコード)
[[9.9999666e-01 1.6275737e-06 1.6897608e-06]
 [1.0858843e-05 9.9997306e-01 1.6078509e-05]
 [1.0666966e-05 1.2271330e-02 9.8771799e-01]]
エンコードした値
[[1.8855007e-02 3.9558447e-08]
 [9.2018157e-01 1.9496704e-03]
 [9.9969387e-01 9.8213345e-01]]
エンコードされた値をもとに、デコードした値
[[9.9999666e-01 1.6275737e-06 1.6897608e-06]
 [1.0858843e-05 9.9997306e-01 1.6078509e-05]
 [1.0666966e-05 1.2271330e-02 9.8771799e-01]]
ソースコード

訓練結果のloadコマンドの引数ファイルが、2年前とは変わっていた。

import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF


def original_network(x, test=False):

    # Edit画面から、右クリック→Export→Python Code(NNC)
    # 引数yは削除
    # def original_network(x, y, test=False):
    # Input:x -> 4
    # Affine -> 100
    h = PF.affine(x, (100,), name='Affine')
    # ReLU
    h = F.relu(h, True)
    # Affine_2
    h = PF.affine(h, (100,), name='Affine_2')
    # ReLU_2
    h = F.relu(h, True)
    # Affine_6
    h = PF.affine(h, (100,), name='Affine_6')
    # ReLU_3
    h = F.relu(h, True)

    # Affine_3 -> 2
    h = PF.affine(h, (2,), name='Affine_3')
    # Sigmoid
    h = F.sigmoid(h)

    # Affine_7 -> 100
    h = PF.affine(h, (100,), name='Affine_7')
    # ReLU_5
    h = F.relu(h, True)
    # Affine_4
    h = PF.affine(h, (100,), name='Affine_4')
    # ReLU_6
    h = F.relu(h, True)
    # Affine_8
    h = PF.affine(h, (100,), name='Affine_8')
    # ReLU_4
    h = F.relu(h, True)
    # Affine_5 -> 3
    h = PF.affine(h, (3,), name='Affine_5')

    # Softmax
    h = F.softmax(h)

    # BinaryCrossEntropy
    # h = F.binary_cross_entropy(h, y)
    return h


def encorder_network(x, test=False):
    # original_networkの前半
    # Input:x -> 4
    # Affine -> 100
    h = PF.affine(x, (100,), name='Affine')
    # ReLU
    h = F.relu(h, True)
    # Affine_2
    h = PF.affine(h, (100,), name='Affine_2')
    # ReLU_2
    h = F.relu(h, True)
    # Affine_6
    h = PF.affine(h, (100,), name='Affine_6')
    # ReLU_3
    h = F.relu(h, True)

    # Affine_3 -> 2
    h = PF.affine(h, (2,), name='Affine_3')
    # Sigmoid
    h = F.sigmoid(h)

    return h


def decorder_network(x, test=False):
    # original_networkの後半
    # Input:x -> 2

    # Affine_7 -> 100
    h = PF.affine(x, (100,), name='Affine_7')
    # ReLU_5
    h = F.relu(h, True)
    # Affine_4
    h = PF.affine(h, (100,), name='Affine_4')
    # ReLU_6
    h = F.relu(h, True)
    # Affine_8
    h = PF.affine(h, (100,), name='Affine_8')
    # ReLU_4
    h = F.relu(h, True)
    # Affine_5 -> 3
    h = PF.affine(h, (3,), name='Affine_5')

    # Softmax
    h = F.softmax(h)

    return h


# https://support.dl.sony.com/docs-ja/%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB%EF%BC%9Aneural-network-console%E3%81%AB%E3%82%88%E3%82%8B%E5%AD%A6%E7%BF%92%E6%B8%88%E3%81%BF%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%A9/
# nn.load_parameters(‘{training result path}/results.nnp’)
nn.load_parameters(
    r"C:\dev\SampleCodes\hello_autoenc\hello_autoenc.files\20200523_112705\results.nnp")

x = nn.Variable((3, 4))
x.d = [4.3, 3, 1.1, 0.1], [4.9, 2.4, 3.3, 1], [4.9, 2.5, 4.5, 1.7]

print("入力データ(アヤメ3種類それぞれ1個づつ)")
print(x.d)

y_origin = original_network(x, test=True)
y_origin.forward()
print("もともとのネットワークの計算結果(エンコード+デコード)")
print(y_origin.d)

y_encoder = encorder_network(x, test=True)
y_encoder.forward()
print("エンコードした値")
print(y_encoder.d)

y_decoder = decorder_network(y_encoder, test=True)
y_decoder.forward()
print("エンコードされた値をもとに、デコードした値")
print(y_decoder.d)

seabornを使ってみた

seabornを使ってみた。
plt.show(block=False) # これがないと表示されない。block=Falseがないとグラフを閉じるまでプログラムの実行が一時停止する。

ソースコード
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt

x = np.random.normal(size=100) 
iris = sns.load_dataset("iris")

sns.distplot(x, kde=False, rug=False, bins=10)
plt.show(block=False) # これがないと表示されない。block=Falseがないとグラフを閉じるまでプログラムの実行が一時停止する。


sns.pairplot(iris)
plt.show(block=False)

# プログラムの終了とともにプロット画面も閉じてしまうので入力まち
input()
結果

f:id:Cucco:20200327183347p:plain
distplotの結果

f:id:Cucco:20200327183425p:plain
pairplotの結果

NasneからNASへの移行(速報版)

とりあえず最低限の目途はついたので、NasneからNASへのデータ移行メモ。
用途としては、録画データの延命。Nasneは古くなっていて、いつ壊れてもおかしくないので。

まだNasneからコピーしていない録画番組を選んでコピーや、コピーした番組の番組名ごとのディレクトリ分けのスクリプトPythonで作成。もう少しきれいにしてから掲載予定。

移行先のNAS

QNAPのTS-453be。本体5万強。HDD別。4本必要。
TS-453Be | 幅広いアプリケーション拡張およびより優れた効率性を引き出すためのPCIeスロット付きのクアッドコアマルチメディアNAS | QNAP

追加購入のQNAP NASのアプリ。sMedio DTCP Move。税込み1100円。
sMedio DTCP MOVE

iPhoneでの再生環境用 税込み 1300円。※ 2020年12月時点で、iPadでは再生できないことを確認。
Media Link Player for DTV ~スマホでテレビを見よう~


できないこと
  • NASにコピーした番組は、NASから再移動できない。DTCP-IPとかDLNAとかの仕組みの範囲で、wifiのある範囲なら再生できる。
  • NASの装置自体が壊れたら(装置を交換したら)、録画データは再生できない。
できること
  • NasneからNASに番組をコピー(ダビング10ならコピー制限数が減っていく)
  • HDDが壊れた時のRAIDの再構築(推定)
  • iPhoneでの視聴
標準ではできないこと
  • コピー候補の番組を絞り込むこと。
    • 録画した月やジャンルでの絞り込みはできる。
    • 番組名で検索してコピー候補の絞り込みはできない。
  • まだNasneからコピーしていない番組を選んでコピーすること。
  • 全件の一括コピー。「Nasneのデータを全件コピーしろ」という操作はできるのだが、150件くらいしかコピーされなかった。(※1)
スクリプトで解決できたこと
  • まだNasneからコピーしていない録画番組を選んでコピーすること。ただし※1を避けるため、100件単位。
  • コピーした番組の番組名ごとのディレクトリ分け。手動で番組名に含まれる名前のフォルダを作っておく必要あり。
スクリプトで解決できてないこと
  • まだNasneからコピーしていない録画番組の”全件”をコピーすること。(とりあえず100件、残があればもう1回実行、とかそういう使い方で回避。)
試行錯誤メモ
  • iPhoneのDiXiM Playは、モバイル向けの解像度を半分にしたトランスコード版なら再生できる。
    • NasneNASに移動の操作をする際に設定すればトランスコード版で移動できる。
    • sMedio DTCP Moveにはトランスコード機能はないらしく、コピー後にあとからトランスコードできない。
    • データ容量はだいたい1/4になる。
    • DiXiM Playでも設定が必要で、「再生判定を厳しくする」をオフに。

iPhoneのバックアップ先をDドライブへ

iPhoneのバックアップデータがCドライブの容量を食うので、iTunesが書き込むiPhoneのバックアップ先のディレクトリをDドライブに移動させる話。(公式ではないので自己責任で)

手順

大まかには、シンボリックリンクをつかって、iTunesが書き込むiPhoneのバックアップ先のディレクトリを別のドライブのディレクトリに化かす。
Windows10 Windowsストア版のiTunesむけ。

  1. 既存のバックアップディレクトリを、別のドライブにコピーする。
  2. 既存のバックアップディレクトリをリネームする(最終的には削除する)
  3. 既存のバックアップディレクトリから、別のドライブへのシンボリックリンクを作る
01. 既存のバックアップディレクトリを、別のドライブにコピーする。

既存のバックアップディレクトリ: "C:\Users\<ユーザディレクトリ名>\Apple\MobileSync\Backup"
から、
別のドライブ: "D:\keep\iTunesBackup"
に既存のバックアップファイルをコピーする。

robocopy /MIR "C:\Users\<ユーザディレクトリ名>\Apple\MobileSync\Backup" "D:\keep\iTunesBackup"

コピー完了後に、既存のバックアップディレクトリと別のドライブの容量やファイル数が一致することを確認する。

02. 既存のバックアップディレクトリをリネームする(最終的には削除する)

この後、既存のバックアップディレクトリと同名のシンボリックリンクを作るので、既存のバックアップディレクトリはなんでもいいので、別名にしておきます。

既存のバックアップディレクトリ: "C:\Users\<ユーザディレクトリ名>\Apple\MobileSync\Backup"
リネーム後: "C:\Users\<ユーザディレクトリ名>\Apple\MobileSync\Backup-old"

03. 既存のバックアップディレクトリから、別のドライブへのシンボリックリンクを作る

以下を、管理者として実行します。

mklink /d "C:\Users\<ユーザディレクトリ名>\Apple\MobileSync\Backup" "D:\keep\iTunesBackup"

最後に

iTunesiPhoneのバックアップを取る。別のドライブにあるバックアップファイルが更新されていればOK。

openpyxlで、エクセルファイルのフィルタの設定をする

openpyxlで、エクセルファイルのフィルタの設定をする実証コード

エクセルで開いても、フィルタの設定は入っているが、フィルタ表示はされていない状態で表示される。
エクセルで開いて、フィルタのプルダウンを開いて、再適用すれば、フィルタリングされた状態で表示される。

コード
from openpyxl import Workbook, load_workbook, utils

# 1行目がヘッダで、A2からデータが入っている想定
read_filename = r"C:\dev\SampleCodes\hello_openpyxl\NATO_phonetic_alphabet.xlsx"
save_filename = read_filename.replace(".xlsx", "_filterd.xlsx")
wb = load_workbook(read_filename)

# 対象シートは 'Sheet1'
ws = wb['Sheet1']

# フィルタを設定したい列名を指定
filter_col = 'A'
# column_index_from_string(A)は1を返すが、使うときはオフセットで0始まりなので1を引いておく。
filter_col_offset = utils.column_index_from_string(filter_col) - 1
print("filter_col = {0}, filter_col_offset = {1}".format(
    filter_col, filter_col_offset))

# 表示したい行にある、A列の内容
# 実際にはない値を指定しても、add_filter_columnでは無視される。
filter_words = ["0", "A", "Z"]

# データがある最大行を取得
max_row = ws.max_row
max_col = ws.max_column
print("max_row = {0}, max_col = {1}".format(max_row, max_col))

# 表示したい値が、実際にあるか確かめておく
for word in filter_words:
    flag_find = False

    for row_index in range(0, max_row):
        cell_value = ws.cell(
            row=row_index+1, column=utils.column_index_from_string(filter_col)).value
        if word == str(cell_value):
            flag_find = True
            print("{0} in filter_words found".format(word))
            break

    if flag_find == False:
        print("Error!! {0} in filter_words not found".format(word))
        exit(-1)

# 列番号をxlsの列名アルファベットに変換
max_col_letter = utils.cell.get_column_letter(max_col)

# filterの範囲を指定
# A列からデータがあることが前提。
ws.auto_filter.ref = "A1:" + max_col_letter + str(max_row)
print("ws.auto_filter.ref = {0}".format(ws.auto_filter.ref))

# filter指定
# filter_col_index は 0だと、ws.auto_filter.refの範囲で、いちばん左の列
ws.auto_filter.add_filter_column(filter_col_offset, filter_words)
# サンプルコードにあったソートはしなくてよい。
# https://openpyxl.readthedocs.io/en/latest/filters.html#using-filters-and-sorts
# ws.auto_filter.add_sort_condition("B2:B15")

# 保存はする。
# エクセルで開いても、フィルタの設定は入っているが、フィルタ表示はされていない状態で表示される。
# エクセルで開いて、フィルタのプルダウンを開いて、再適用すれば、フィルタ表示される。
wb.save(save_filename)

wb.close()
実行結果
(base) C:\dev\SampleCodes\hello_openpyxl>python hello_filter.py
filter_col = A, filter_col_offset = 0
max_row = 37, max_col = 2
0 in filter_words found
A in filter_words found
Z in filter_words found
ws.auto_filter.ref = A1:B37

(base) C:\dev\SampleCodes\hello_openpyxl>
読んでいる元データ

NATO_phonetic_alphabet.xlsx

f:id:Cucco:20191018222930p:plain
読んでいる元データ