>& STDOUT

主にソフトウェアに関する日々の標準出力+標準エラー出力

Unityで一通りのゲームループを作るのに学んだことメモ

Unityですごく簡易な3Dシューティングを作ってみました。以下のような要素を持っています。

youtu.be

  • タイトル画面、ゲーム画面(インゲーム)、ゲームオーバー(クリア)画面がある
  • プレーヤーはX軸移動と、スペースキーでショットが打てる
  • 画面にはボスキャラと隕石があり、ボスキャラは自機にランダム周期で攻撃してくる
  • 画面内のすべてのオブジェクトを倒すとクリア
  • スコアシステムがある
  • ゲームオーバー(クリア)画面ではスコアが表示され、再挑戦か、終了がキーで選べる

Unityにほぼ触ったことがない状態から、以下のようなことを学びつつ作りました。いったんここまで学んだことをセーブします。

学んだこと

エディタの使い方

何気に3D空間で自由に(見たいように)視点を切り替えて、オブジェクトを操作、編集する部分をしっかりやったのが以降のすべての作業の効率化に繋がったと思います。インスペクタやPrefabの使い方、アタッチやコンポーネントの追加など、基本的な部分は動画で学べました。

キーで自機を動かす

自機のオブジェクト(最初はCube、後でアセットに差し替え)を作成し、Playerスクリプト(Player.cs)を作成してアタッチ。updateで、キー入力を Input.GetKey(KeyCode.RightArrow) で捕まえて transform.position += Vector3.right * 0.02f;で動かす。動かすついでに、transform.rotation = Quaternion.Euler(0, 0, -10);で傾ける。キー入力を捕まえるのはupdateでよいけれど、動かしたり傾けたりするのは、fixedUpdateのほうががよさそう。

自機から弾を撃つ

同じく、updateで、 Instantiate(tama, transform.position + Vector3.forward * 2f, Quaternion.identity); などとする。tama はPrefabで予め用意しておく。 Vector3.forward * 2f で *2f しているのは、自機の場所から弾を出すと自機とのコリジョン判定で弾が消えてしまうから。弾側には、生成されたら一定速度で奥に飛んでいき、何かとぶつかったら自分(自機弾)をDestroyする、という処理が書いてあります。

隕石(お邪魔キャラ)を配置して動かす

自機と同様にオブジェクトとスクリプトを用意した後、Updateで、transform.position = new Vector3(pos.x + Mathf.Sin(Time.time) * moveWidth, pos.y, pos.z); などとして、positionをVector3型のpos.xに正弦波の時間軸を取って、それに幅を掛けて動作させる。Vector3オブジェクトはupdateを抜けると破棄されるので、毎回newしてても大丈夫。

自機の弾にはタグが打ってあり、自機の弾に触れた時のみ、Destoryします(ボスの弾で消えてほしくないので)。また、自分が破壊されたときは、ゲームの状態を管理しているオブジェクト(GameMaster と名付けた空のオブジェクト)に、+10点されたよと、自己申告します。

ボスを設定する

隕石をベースに、さまざまなフィーチャーが追加されています。左右に移動する部分は、隕石と同じです。加えて、ボス自身が自機側(手前)に向けて、不定の間隔で弾を撃ってきます。

弾の発射については、不定の間隔で行いたかったので、その間隔の算出と弾の発射をコルーチン側(非同期処理)で行っています。これに関しては、updateで Random.Range(0.05f, 1.7f); の間射出を休ませる、という書き方でもよかったかもです。

    private IEnumerator bossTamaShot()
    {
        while(true) {
            float shortDuration = Random.Range(0.05f, 1.7f);
            yield return new WaitForSecondsRealtime(shortDuration);
            Instantiate(enemyTama, transform.position + Vector3.back * 10f, Quaternion.identity);
        }
    }

また、1発で破壊される隕石と異なり、ボスにはヒットポイントが設定されています。自機弾がヒットするたびに減っていき、0になったら爆発のエフェクトと一緒にDestoryされ、GameMasterに得点を自己申告します。シューティングゲームでは、1発で倒せない敵については、自機弾が確かにヒットしていることを何らかの形で示す必要があり、本作ではよくある「ヒットしたときに一瞬全体が赤くなる」という処理を、これもコルーチンで実装しています。ベースのマテリアルを取っておいたあと、赤のマテリアルに差し替えて、0.08秒待って元のマテリアルに戻します。

    private IEnumerator flashColorRedOnCollisionEnter()
    {
        Color baseColor = GetComponent<Renderer>().material.color;
        GetComponent<Renderer>().material.color = Color.red;
        yield return new WaitForSecondsRealtime(0.08f);
        GetComponent<Renderer>().material.color = baseColor;
    }

最近のシューティングゲームではボスの体力ゲージが表示されていることが多いです。今回は Slider 画面上部に配置して、ボスのヒットポイントと連動する形で実装しました。Start で slider.maxValue = hitPoint;として初期化して、Playerの弾が当たったときにslider.valueを現在のヒットポイントに書き換える形で実装しています。

各種効果音を鳴らす

3D空間でオブジェクトの場所で音を鳴らすだけなら割と簡単でした。効果音を発生させたいオブジェクトにアタッチしているスクリプトpublic AudioClip playerShotSe; としてインスペクタで鳴らしたい音を指定。あとは鳴らしたいタイミングで、AudioSource.PlayClipAtPoint(playerShotSe, transform.position); とするだけです。ただ、この方法だとボスのように奥にいるキャラの爆発音が小さく聞こえてしまう問題が発生しました。そこで、transform.position + Vector3.back * 40f として、ボスの破壊音だけはだいぶ手前で鳴るようにしてみました。物理的には不自然ですが、ゲーム的には自然になったと思います。

ゲームの状態をコントロールする

これまで何度か出てきましたが、ゲームのメイン画面には、GameMasterという不可視の空オブジェクトがいて、この人がインゲームの状態を管理しています。例えば、ボスと隕石がすべて無くなればゲームクリア画面に遷移、プレーヤーが被弾したらゲームオーバー画面に遷移する、スコアを集計し、スコア表示のオブジェクトに渡したりといった役割を担っています。

うっかり?GameMasterという名前にしてしまいましたが、Unityのお作法的には GameManager という名前が適切なようです。この名前にすると、アイコンが自動的に歯車になります。

シーンをまたいでゲームのデータを受け渡す

ScriptableObject という仕組みを利用しました。ゲームクリア画面とゲームオーバー画面はシーン自体は同じで、GameMasterがどちらかの画面に遷移させるときにテキストだけ変えています。また、いずれの画面でもスコアが表示されるため、スコア情報もGameMasterが常にScriptableObjectに保存しています。

余談ですが、スコアのデータをCanvas以下のTMPに代入しようとして、そのTMPオブジェクトが取れておらずNullReferenceになるというエラーを、ScriptableObjectが適切に受け渡されていないエラーと勘違いして、結構な時間ハマりました。

ゲームオーバー(クリア)画面で、再挑戦か終了かをUIで選ぶ

上下のキー操作を取得し、buttons[currentButtonIndex].Select();を発火します。onClickに、シーンの再ロードや、アプリケーションのQuitを行う関数が設定されています。メニューの数が少ないことから、かなりネイティブなつくりをしてしまった感があります。これはちゃんとライブラリを学んだ方がよさそうです。

まとめ

Unityにはビジュアル開発環境もありますが、今回はC#でゴリゴリ書く方でやってみたところ、他言語の経験があれば、Unityの中で扱う分にはほぼ言語系で困ることはありませんでした。規模が小さいからというのもありますが、それでも13個ぐらいのスクリプトファイルから構成されています。すごく小さなゲームですが、ありそうな要素を概ねカバーして、ゲームループを1周させてみるのは開発環境を学ぶのに非常に役に立ちました。

参考

ここまでの内容は、以下、ITeens Labのこば先生の動画の15回までをベースにしています。本当にむちゃくちゃ分かりやすいです。
超初心者向けUnityの使い方① 〜Unity(ユニティ)とは? インストールと準備〜 - YouTube https://www.youtube.com/watch?v=8h2JqLg_oVI

効果音は、効果音工房様の素材を利用させていただきました。ありがとうございます。
効果音工房 | 自由に使える無料の効果音を配布中! https://umipla.com/

「lsコマンド」をテストする

VeriServeのカレンダー | Advent Calendar 2022 - Qiita の記事です。

みんな大好き ls (list segments) に対してテストを考えてみました。例えばこういうアプローチがあるよね、ということで、テスト開発プロセスを一定カバーする形になっています。

概要はこちら:
https://github.com/snsk/ls_testing
テスト開発プロセスの概要はこちら:
https://github.com/snsk/ls_testing/wiki/test_development

本稿は上記*1の解説記事です。

なお、すべてのテストは自動化されており、Github Actions上で動作します。ファジングなどとても時間がかかるテストは並列で実行されるように設定しています。


テストアーキテクチャ設計

テスト要求は「任意のディストリビューション、バージョンのlsコマンドがGNUドキュメントに記載の仕様を満たしているか」と暗黙に置いています。

今回のテスト開発では、要求に従った機能テストと、日常的に利用されるコマンドであるがゆえの頑健性のふたつを柱として掲げています。これらの特性に基づいて、どんなテストタイプをどんな優先度で、どれぐらいのボリュームで行うのかを示す図を考えてみました(今回は、柱として挙がりそうな Security/Performance/Usability については対象外としています)。テストアーキテクチャをどのように表現すると良いのか、については議論がたくさんあると思いますが、個人的には「どんなテストタイプを、どんな優先度で、どれぐらいやる?」が分かると嬉しいなと考えます。

テスト設計から実装

機能仕様たち

このアーキテクチャ図では末端の要素がテストタイプになっており、最優先の(左上に位置する)ものは 機能テスト::仕様準拠::基本的なふるまい(Functional_SpecConfirmance_BasicBehavior) となっています。仕様準拠のコンテナは自然言語で書かれた仕様をCFDで分解して解釈し、デシジョンテーブルに変換(これはGIHOZがやってくれる)してからテストケースまで落とす、という形にしています。High Level Test Cases をCFD->DTという流れですね。Low Level Test Cases は behave というPythonのBDDフレームワークを使って処理系で実行可能な形で実装しています。CLIアプリケーションは自動化しやすくて良きです。


引数の組み合わせテスト

linuxコマンドの多くは引数で指定されるオプションでそのふるまいを変化させます。lsコマンドにも無数のオプションがあり、実は今回一番lsコマンドを選んで後悔した部分です。GNU仕様には、複数の引数を組み合わせたときにどう振る舞う?という仕様が一部しかありません(仕様に書かれているものは仕様準拠でカバーしています)。つまり、大部分は期待動作がない形になります。そんなときには、ということで代表的なテストオラク錬金術である「現在の振る舞いを一旦の期待動作とする」形でアプローチしました。
具体的には

arg_combi_list = list(itertools.combinations(args ,2))

としてふたつの水準同士の組み合わせを作って特定のディストリビューションに搭載のlsコマンドで出力を確認。これを期待動作リストとして、コマンド実行と出力比較をイテレートするという実装になっています。このへんはテスト設計の戦略部分を含み、検討の余地があるなあと思っています。

頑健性たち

僕が大好きファジング無双のコーナーです。lsコマンドに対する「入力」は、「ディレクトリやファイルの構造」になるかなと考え、さまざまなディレクトリ、ファイル構造を生成してlsコマンドに食べさせて、クラッシュやフリーズが発生しないこと、という頑健性の基本的な要素を確認するテストを考えて実装しています。また、引数の組み合わせによる動作不良や、OSの権限周り、存在しないディレクトリの指定などのノイズ、通常想定されない長い長いパスなどのアクティブノイズなどをノイズ系としてテストタイプに配置しています。

「任意のディレクトリ、ファイル構造」をどうやって生成しようかと悩んでいたところ、randomfiletree という今回の目的に対して神がかったライブラリを発見できたので、このファザーの実装が大変楽になりました。ありがとうございます。今回は、この神ライブラリで生成したディレクトリ構造を、なるべく表示量の多い引数構成のlsコマンドに食べさせる、という単純な構成でしたので、ファザーは自前で実装しています。

シノプシス社によるファジングの成熟度レベル*2としてはレベル1とレベル2の間ぐらいです。どちらも一定時間、あるいは10万~100万回の実行が求められますが、Github Actionsの時間を使い切ってしまうので、1000件~10000件に抑えています。機構的には実行可能です。

ノイズの洗い出しは、ラルフチャートを使ってこれまでにフォローされている入力種別を一旦整理したうえで、ノイズ、アクティブノイズにはどんな入力が利用できそうかをまとめています。これは、機能テストでも利用したBDDフレームワークを使って実装しています。


まとめ

「lsコマンド」に対してテスト開発プロセスの基本設計から実装までを一通り流してみました。こういうテストタイプにはこういうテスト技法が利用できるというのを一概に決めにくいのがテスト開発プロセスの悩みでもあり、創造的な部分でもあるのかなと思います。一方、テストタイプはあくまでテストアーキテクチャ設計(上流)のいち要素でしかないので、そこからモデルを起こしてカバレッジクライテリアを考えていかないと技法(実装技術)の選択は難しいよね、というプログラミングであっても同様に発生する課題でもあるかなと思います。

linuxコマンドはソースコードもドキュメントも誰でも触れるので、テスト開発プロセスを個人で気軽に回してみるためのSUTとして優れていると思います。ただ、lsコマンドはちょっと複雑すぎたかも知れません。もしこれを読んで自分もやってみようかな、と思われたかたは、wcコマンドあたりが仕様の大きさや、機能の明確さの面からもおすすめです。

*1:Github側が英語になっているのはテストベースが英語だったから、です。日本語で書けば良かったです

*2:https://www.synopsys.com/content/dam/synopsys/sig-assets/whitepapers/fuzz-testing-maturity-model.pdf

テスト設計に連動してテストコードを動的に変更する

テスト設計の変更に従ってテストコードが自動で変更されて、勝手にテストを実行してくれると嬉しいですよね。その一部として、GIHOZのAPIを使って、期待動作の変更によってテストコードのふるまいを変えるデモを実演してみます。

まずふるまいから

題材のfizzbuzz関数に対して、デシジョンテーブルでド正常系のテストケースを4つ定義します。

テストを走らせると全てPASSします。

なんらかの仕様変更で、現在fizzと表示すべき振る舞いがbuzzに変更されたとして、デシジョンテーブルにそれを反映します。

GIHOZ側のモデルで振る舞いが変更されたので、これと連動してテストがコケます。ポイントは、GIHOZ側のモデルを変更しただけで、テストコードは一切触っていない、点です。TestOpsぽいですね。


仕組み

GIHOZのAPIを利用して、デシジョンテーブルのデータを取ってきて、動作記述部の値をテストコードのexpectに指定します。pytest の @pytest.mark.parametrize デコレータにテストデータ、期待動作のリストを渡してあげるとリストの要素数だけテストを実行してくれるので、簡単なデータ駆動テストを実現できます。

コード

GIHOZのAPIキーは各自 secret.yml に記載してください。本記事の趣旨に沿って分かりやすいように、1ファイルにまとめています。GIHOZからデシジョンテーブルのデータを取得してきて、テスト対象のfizzbuzz()に対してpytestでテストを走らせています。

import requests
import yaml
import json
import pytest

#テスト対象の関数
def fizzbuzz(num):

    if(num%3==0 and num%5==0):
        return "fizzbuzz"
    elif(num%3==0):
        return "fizz"
    elif(num%5==0):
        return "buzz"
    else:
        return num

#GIHOZからAPIでテスト設計データを取得
with open('secret.yml', 'r') as yml:
    config = yaml.safe_load(yml)

headers = {'Authorization': 'Bearer {}'.format(config['api_key'])}
data = requests.get(
    'https://gihoz-api.veriserve.co.jp/api/v1/users/snsk/repositories/MyRepository/test_cases/180acbf7-56dc-498e-b880-bc94952c6e44'
    ,headers=headers
)
data_json = json.loads(data.text)
test_case_json = json.loads(data_json["data"]["attributes"]["test_case_json"])

#テストケースに取得したテスト設計データから期待動作を埋め込む
test_case = [
    (3, test_case_json["data"]["actionRows"][0][1]),
    (5, test_case_json["data"]["actionRows"][1][1]),
    (15,test_case_json["data"]["actionRows"][2][1]),
    (1, 1),
]

#テスト実行
@pytest.mark.parametrize("num, expect", test_case)
def test_fizzbuzz(num, expect):
    assert fizzbuzz(num) == expect

イケてないところ

今回は、テストケースの条件部分を即値で指定しています。これは本来テストモデルから取得するべきですが、現状のGIHOZのAPIで返ってくるjsonからは読み取りが難しそうでした。現状のGIHOZのjsonはviewを構成するための構造になっているので、よりテストケースに向けた構造になると、このような仕組みからも扱いやすくなるかなと思います。フィードバックしました。

参考

書籍「AIソフトウェアのテスト」 メモ

AIソフトウェアに対する4つのテストのアプローチを具体的なモデル(MINSTの数字認識、HouseSaleデータセット)を用いてチュートリアル付きで解説してくれる稀有な書籍。を通して読んで(やって)みたのでメモ。

1.メタモルフィックテスティング

  • メタモルフィックテスティングでは、仕様に基づく入出力の突合せ、入出力の関係性の変化に着目する。 テストオラクルを得にくい状況で活用できる。 テストデータxとxを加工したテストデータx'を入力したとき得られるy, y'の間に関係式が成立すればそれを「メタモルフィック関係」と呼べる。

  • ソース入力データ(x), フォローアップ入力データ(y)

    • ソース入力データ:元の学習データ
    • フォローアップ入力データ:元の学習データを加工したもの

メタモルフィックテスティングが有効活用できる条件

  • 正解データが付与されていないテスト用入力データが多数ある
  • 特定の特徴を持つテスト用入力データの数が不足している
  • 既存のテスト用データを加工することでその特徴を持つテスト用入力データを生成できる
  • 既存のテスト用入力データに対して、AIソフトウェアが正しい出力データを返すことを、従来型テストで確認済みである。

2.ニューロンカバレッジ

  • ニューロンができるだけ多く活性化するテストデータを考える。非活性のニューロンが活性化する入力データを作ることで非常にまれな誤りを検出できる。これを主張している論文もある。

DeepXplore: Automated Whitebox Testing of Deep Learning Systems : https://arxiv.org/abs/1705.06640

DeepTest: Automated Testing of Deep-Neural-Network-driven Autonomous Cars : https://arxiv.org/abs/1708.08559

  • ニューロンカバレッジを向上させる方法として、実際に学習モデルにデータを与えてみる方法と、非活性ニューロンの値を偏微分して傾きを得ることで、非活性ニューロンを活性化させる加工の方針を得ることができる。

  • データの加工方針はどのようなものでもよい、というわけではない。「そのDNNモデルはどのような入力データを推論対象として受け付けるか」の想定に基づく必要がある。

加工ベクトルと方針ベクトル

  • 非活性ニューロンxが活性化しやすいと考えられるベクトルが「方針ベクトル」。「方針ベクトル」に従って実際に作成するデータがもつベクトルが「加工ベクトル」の内積が大きいデータを採用する。

3.最大安全半径

  • 分類問題を解くAIソフトウェアにおける入力データxから得られる推論結果がyだとして、このxから任意の距離εにあるx'を入力してyが得られるならこの距離εは入力データxに対する安全半径。安全半径は距離εにおいて複数考えることができて、そのうち最大のものが、最大安全半径。

  • 最大安全半径はロバスト性を表す指標として利用される。

  • DNNモデルの最大安全半径を計算するDNN-Certという手法がある。DNNの最大安全半径を正確に計算する方法もあるが、膨大な時間がかかる。DNN-Certは現実的な時間で近似値を導く。DNN-Certでは最大安全半径より小さい値を導く。本当の最大安全半径はDNN-Certで求められる近似値より大きいことが保障される。つまり、この近似値のなかに敵性データはない

  • DNN-Cert の出力例

tools\cnn_cert>python run_mymodel.py ..\model\mnist\keras\model_mnist_keras.h5 10

generating labels...
[DATAGEN][L1] no = 1, true_id = 0, true_label = 7, predicted = 7, correct = True, seq = [6], info = ['random']
[DATAGEN][L1] no = 2, true_id = 1, true_label = 2, predicted = 1, correct = False, seq = [], info = []
[DATAGEN][L1] no = 3, true_id = 2, true_label = 1, predicted = 1, correct = True, seq = [0], info = ['random']
[DATAGEN][L1] no = 4, true_id = 3, true_label = 0, predicted = 0, correct = True, seq = [8], info = ['random']
[DATAGEN][L1] no = 5, true_id = 4, true_label = 4, predicted = 4, correct = True, seq = [6], info = ['random']
[DATAGEN][L1] no = 6, true_id = 5, true_label = 1, predicted = 1, correct = True, seq = [6], info = ['random']
[DATAGEN][L1] no = 7, true_id = 6, true_label = 4, predicted = 4, correct = True, seq = [9], info = ['random']
[DATAGEN][L1] no = 8, true_id = 7, true_label = 9, predicted = 9, correct = True, seq = [8], info = ['random']
[DATAGEN][L1] no = 9, true_id = 8, true_label = 5, predicted = 6, correct = False, seq = [], info = []
[DATAGEN][L1] no = 10, true_id = 9, true_label = 9, predicted = 7, correct = False, seq = [], info = []
labels generated
7 images generated in total.
--- CNN-Cert: Computing eps for input image 0---
Step 0, eps = 0.05000, 6.3401 <= f_c - f_t <= 6.7005
Step 1, eps = 0.13591, 5.4847 <= f_c - f_t <= 7.5599
Step 2, eps = 0.36945, -4.861 <= f_c - f_t <= 17.068
Step 3, eps = 0.22408, 3.1541 <= f_c - f_t <= 9.7036
Step 4, eps = 0.28773, 0.2217 <= f_c - f_t <= 12.343
Step 5, eps = 0.32604, -2.026 <= f_c - f_t <= 14.403
Step 6, eps = 0.30629, -0.816 <= f_c - f_t <= 13.290
Step 7, eps = 0.29686, -0.275 <= f_c - f_t <= 12.795
Step 8, eps = 0.29226, -0.022 <= f_c - f_t <= 12.564
Step 9, eps = 0.28999, 0.1008 <= f_c - f_t <= 12.452
Step 10, eps = 0.29112, 0.0393 <= f_c - f_t <= 12.508
Step 11, eps = 0.29169, 0.0084 <= f_c - f_t <= 12.536
Step 12, eps = 0.29198, -0.006 <= f_c - f_t <= 12.550
Step 13, eps = 0.29183, 0.0007 <= f_c - f_t <= 12.543
Step 14, eps = 0.29190, -0.003 <= f_c - f_t <= 12.547
[L1] method = CNN-Cert-relu, model = ..\model\mnist\keras\model_mnist_keras.h5, image no = 0, true_id = 0, target_label = 6, true_label = 7, norm = 2, robustness = 0.29183
--- CNN-Cert: Computing eps for input image 1---
Step 0, eps = 0.05000, 3.9229 <= f_c - f_t <= 4.1984
Step 1, eps = 0.13591, 3.1945 <= f_c - f_t <= 4.9119
Step 2, eps = 0.36945, -6.271 <= f_c - f_t <= 14.365
Step 3, eps = 0.22408, 1.0944 <= f_c - f_t <= 7.0078
Step 4, eps = 0.28773, -1.674 <= f_c - f_t <= 9.7540
Step 5, eps = 0.25392, -0.085 <= f_c - f_t <= 8.1779
Step 6, eps = 0.23854, 0.5403 <= f_c - f_t <= 7.5564
Step 7, eps = 0.24611, 0.2395 <= f_c - f_t <= 7.8552
Step 8, eps = 0.24998, 0.0802 <= f_c - f_t <= 8.0136
Step 9, eps = 0.25194, -0.000 <= f_c - f_t <= 8.0942
Step 10, eps = 0.25096, 0.0398 <= f_c - f_t <= 8.0537
Step 11, eps = 0.25145, 0.0195 <= f_c - f_t <= 8.0739
Step 12, eps = 0.25170, 0.0093 <= f_c - f_t <= 8.0840
Step 13, eps = 0.25182, 0.0042 <= f_c - f_t <= 8.0891
Step 14, eps = 0.25188, 0.0016 <= f_c - f_t <= 8.0916
[L1] method = CNN-Cert-relu, model = ..\model\mnist\keras\model_mnist_keras.h5, image no = 1, true_id = 2, target_label = 0, true_label = 1, norm = 2, robustness = 0.25188

4.網羅検証

  • 学習済みモデルの推論結果について「検証したい性質」を決めて、その性質が常に満たされることを、指定したすべての入力データに対して機械的に検証していく。

  • 入力データの範囲と期待される推論結果の条件を指定し、範囲内のすべての入力データに対して学習済みモデルの推論結果がその条件を満たすことを検証する。

  • AIソフトウェアの特徴として、未知のデータに対してどんな推論が行われるかは常に不明である。したがって、網羅検証では、学習済みモデルが常に正しいことの検証にはなりえない

  • 網羅検証ツールは「検証対象の学習済みモデル」「運用時に入力されるデータと推論結果の情報」「前提条件」「検証性質」の4つの項目を入力すると、検証を実行し、反証が見つかれば出力してくれる。

    • 前提条件には主に入力データの範囲を指定し、検証性質には推論結果の条件を指定する
  • 前提条件をP, 検証性質をI, と置くとき、検証内容は学習モデルが P→I を満たす

  • 与えられた論理式を真とする変数値を定める問題は「充足可能性問題」と呼ばれる
    • A ∧ B を充足する変数は AとBが共に真
    • A ∨ B を充足する変数は Aだけが真、Bだけが真、A、Bともに真の3つ。
    • (A ∧ ¬B)∧(A¬ ∧ B) はAかBのどちらかを真にすると、どちらかが成立しないのでこれを充足する変数は存在しない
  • 論理式の充足可能性を判定するツールとして「SMTソルバ」がある
  • 網羅検証では、入力データの上下限値、論理式に変換した学習済みモデル、前提条件、検証性質の否定、を論理積で結合し、全体が真となるデータを凡例として探索する

例題

  • 検証性質ファイル property.txt には y >= 500000 これは y は50万(ドル)以上である、ことを示す
  • 前提条件ファイル condition.txt には f0 >= 7000 これは居住面積が7000フィート以上である、ことを示す。
  • つまり、この検証性質と前提条件は、居住面積が7000フィート以上の物件は必ず50万ドル以上であることを指定している。

ツールの実行例

C:\path_to\tools\xgb_verification\example\housesales>
C:\path_to\tools\xgb_verification\example\housesales>python run_housesales.py
data_list_path : C:\path_to\tools\xgb_verification\example\housesales\input_output.json
prop_path : C:\path_to\tools\xgb_verification\example\housesales\property\property.txt
cond_path : C:\path_to\tools\xgb_verification\example\housesales\condition\condition.txt
system_timeout : None
search_timeout : None

Verification starts
Violation found: 1
f0:7000
f1:0
f2:520
f3:1
f4:1
f5:290
f6:0
y:-479061.58692812

===============Result===============
Number of executions of SMT solver : 1
SMT solver run time (s) : 0.008173227310180664
Total run time (s) : 0.7056269645690918
====================================

Violation found: 1 なので、反証が1つ見つかったことになる

f0:7000 #居住面積(平方フィート)
f1:0 #寝室の数
f2:520 #土地面積(平方フィート)
f3:1 #土地の状態
f4:1 #構造とデザインのグレード
f5:290 #地上階の面積(平方フィート)
f6:0 #地下室の面積(平方フィート)

条件非適合範囲の探索

  • 学習済みモデルが検証性質を満たさない場合の「検証性質を満たさない入力データの範囲」を特定する方法

  • 例題では、居住面積が7000フィート以上の物件は50万ドル以上であることを仮定したときに1つの反例が見つかったので、どのような入力データであれば満たされないか?を検索する

  • \tools\xgb_verification\example\housesales\config.json の "search_range_ratio" と、"extract_range" を以下のように書き換える。

{
  "search_range_ratio":100,
  "data_list_path": "./input_output.json",
  "prop_path": "./property/property.txt",
  "cond_path": "./condition/condition.txt",
  "extract_range": true
}

実行結果

Range extraction starts
Violation
  f0 : 7000 <= to <= 13540
  f2 : 520 <= to <= 1651359
  f3 : 1 <= to <= 5
  f4 : 1 <= to <= 11
  f5 : 290 <= to <= 9410
  f6 : 0 <= to <= 4820

Verification starts
No violation found

The number of violation ranges is 1
===============Result===============
Violation
  f0 : 7000 <= to <= 13540
  f2 : 520 <= to <= 1651359
  f4 : 1 <= to <= 11
  f6 : 0 <= to <= 4820
Number of executions of SMT solver : 590
Total run time (s) : 2155.718314409256

実行結果が示す内容

  • 居住面積が7000平方フィート以上、13540平方フィート以下
  • 寝室の数が0から33
  • 土地面積が520平方フィート以上、1651359平方フィート以下
  • 建物の状態が1から5
  • 構造とデザインのグレードが1から11
  • 地上階の面積が290平方フィートから9410平方フィート以下
  • 地下室の面積が0平方フィート以上、4820平方フィート以下

  • 構造グレードは1~13なので、構造グレード12,13の物件については50万ドル以上が保証できそう。

  • つまり、この検証性質はグレードの高い住宅について成立しそう

  • ためしに、前提条件で、構造グレードを高めに設定してみる f0 >= 7000 && f4>=12

実行結果

C:\path_to\tools\xgb_verification\example\housesales>python run_housesales.py
search_range_ratio : 100
data_list_path : C:\path_to\tools\xgb_verification\example\housesales\input_output.json
prop_path : C:\path_to\tools\xgb_verification\example\housesales\property\property.txt
cond_path : C:\path_to\tools\xgb_verification\example\housesales\condition\condition.txt
system_timeout : None
search_timeout : None

Verification starts
No violation found

The number of violation ranges is 0

===============Result===============
Number of executions of SMT solver : 1
SMT solver run time (s) : 39.322577238082886
Total run time (s) : 39.687328815460205
====================================

No violation found になった。

DNNモデルの網羅検証

  • 検証したい性質を論理式で表すので、画像を扱うモデルの検証には適さない
  • 住居データをDNNモデルにあてる
  • 入出力データファイル(tools\xgb_verification\example\housesales\input_output.json)には、f0-f6に対応する入出力ノードの情報を書く。
{
  "input": [
    {
      "name": "f0",
      "cont_value_flag": true,
      "type": "int",
      "upper": 13540,
      "lower": 290
    },
    {
      "name": "f1",
      "cont_value_flag": true,
      "type": "int",
      "upper": 33,
      "lower": 0
    },
    {
      "name": "f2",
      "cont_value_flag": true,
      "type": "int",
      "upper": 1651359,
      "lower": 520
    },
    {
      "name": "f3",
      "cont_value_flag": true,
      "type": "int",
      "upper": 5,
      "lower": 1
    },
    {
      "name": "f4",
      "cont_value_flag": true,
      "type": "int",
      "upper": 13,
      "lower": 1
    },
    {
      "name": "f5",
      "cont_value_flag": true,
      "type": "int",
      "upper": 9410,
      "lower": 290
    },
    {
      "name": "f6",
      "cont_value_flag": true,
      "type": "int",
      "upper": 4820,
      "lower": 0
    }
  ],
  "output": [
    {
      "name": "y"
    }
  ]
}
  • XGBoostで作ったモデルと異なり、こちらでは反例が見つからなかった
    • この単一の事例をもってDNNモデルが優れているというわけではない

参考文献

  • 佐藤直人・小川秀人・來間啓伸・明神智之, AIソフトウェアのテスト ──答のない答え合わせ [4つの手法], リックテレコム, 2021

参考文献というか、ほぼ上記の書籍をなぞって理解を記載した。

MacOS Mojave で pip install pygame がエラーになる時

MacOS Mojave に python3.8 を入れた後に、 

pip install pygame

とすると、

 Running setup.py install for pygame ... error
 ERROR: Command errored out with exit status 1:

エラーが出る。exit status 1 はコンパイラなどパッケージ管理の外側で何かが起こっていることが多い。追っていくと案の定、

src_c/_pygame.h:216:10: fatal error: 'SDL.h' file not found

とあるので、SDLの関連ライブラリを入れてあげると

brew install sdl sdl_image sdl_mixer sdl_ttf portmidi
$ pip install pygame
Collecting pygame
  Using cached https://files.pythonhosted.org/packages/0f/9c/78626be04e193c0624842090fe5555b3805c050dfaa81c8094d6441db2be/pygame-1.9.6.tar.gz
Installing collected packages: pygame
    Running setup.py install for pygame ... done
Successfully installed pygame-1.9.6

問題なく完了、なお、この環境では、pyhton3とpip3にそれぞれ、python, pip にエイリアスが貼ってあります。この手の軽量ゲームライブラリはマルチメディアの処理にSDLを使うことが多いのですが、Macがmetalデフォルトになったので、OSには含まれないようになったのですかね。

「現代品質管理総論」メモ

現代品質管理総論 (シリーズ 現代の品質管理)

現代品質管理総論 (シリーズ 現代の品質管理)

ソフトウェア品質に関わる技術者の初歩的かつかなり遠大なテーマである「品質管理」と「品質保証」ってどう違うの?という疑問に、ズドンとくる腹落ちを賜れる著作。いかにも大学の講義で使いそうな装丁ですが、飯塚先生の軽妙かつちょっとだけシニカルな語り口のおかげで案外読みやすいです。読みやすい、だけで内容は濃すぎるので、しっかり時間を確保して読まれることをお勧めします。

「品質確保のための要件」

f:id:shinsuku:20180117124053p:plain

品質技術者として製品、チーム、部門の品質向上を推進するにあたって、①動機 もある、③技術 も何となく整えられてきた、④管理 ⑤ひと もそれなりのリソースをもらっているけれど、どうしても ⑥推進 がうまく進んでいる気がしない時期が数年あったのですが、この図をみて衝撃を受けました。そう、②思想 がないのです。僕自身にも、組織にも。だから、何となく何かをやってる気はするけどこの方向で合っているのか確信が持てなかったんですね。「思想」というと大げさに聞こえるかもですが、見えている範囲(全社、事業部、チームなど)が仕事を成すうえでどんなことを大事にしていきたいか、それがどのように製品に織り込まれるのか(トレーサビリティ)辺りが合意できれば良いのかなと思いました。

品質保証の方法の進展

「検査重点主義」

 米国から近代的品質管理を学んだ第二次世界大戦直後。保証の対象となっている製品の集まり(ロット)に対して、その全部または一部の品質レベルを評価し、一定レベルに達していると判断されたものだけを出荷する。もしかしたら、農業などでは、いまもここに重点を置いているかもですね。

「工程管理重点主義」

 検査には作ってしまった不良品を除く機能しかない。はじめから良品を作るほうが良いに決まっている。ということで、1950年代から製造工程をきちんと管理することによってはじめから良いものを作ろうという考えが広まった。「品質は工程で作り込め」の時代。

「新製品開発重点主義」

 1960年代にはいると、いくら製造工程が整然としていても、不良率がどれだけ低くても、結局売れなければ意味がないよね、という考えが生まれた。真の品質を保証するためには、まず良い製品仕様を作ることが重要である。また、製造工程でのトラブルを分析してみると、その原因の多くは上流工程である生産準備や設計・開発にあることが明らかになってきた。こうして「品質は企画・設計で作り込め」という言葉が生まれた。

経営における三つの管理

こうして日本的品質管理は, 品質を中核とする経営アプローチでありながらも経営管理システム一般に対して重要な概念や方法論について発言するようになり, これがアメリカの品質管理, 経営管理に大きな影響を与えたのである
P.59 3.品質のための管理システム 3.2 管理システムのモデル

静的管理
 タテの管理:日常管理(分掌業務管理、部門別管理)
 ヨコの管理:機能別管理(経営要素管理、管理目的別管理、部門間連携)
動的管理
 方針管理(環境適応型全社一丸の管理)

日常管理

業務目的の明確化、業務プロセスの定義、業務結果の確認と適切なフィードバックを標準化を基盤として実施する。それぞれの部門で日常的に当然実施されなければいけない分掌業務について、その業務目的を効率的に達成するためのすべての活動の仕組みと実施に関わる管理。

機能別管理

品質、コスト、納期、安全・環境などの機能を軸とした部門をまたがるプロセスを想定し、そのプロセスを全社的な立場から管理しようとするもの。意味的には「経営要素」のほうが近い。

方針管理

環境の変化への対応、自社のビジョン達成のために通常の管理体制(日常管理の仕組み)の中で満足に実施することが難しいような全社的な重要課題を、組織を挙げてベクトルを合わせて確実に解決していくための管理の方法論。

静的管理を基本としつつ、変化への対応を余儀なくされる昨今の経営ではより動的管理(方針管理)のウェイトが高まることは想像に難くないです。方針管理については別途まとめたいと思います。

変更管理

本記事の前節からいきなりスケールダウンしますが、気になったのでメモ。

変更によるトラブルには

(a) 変更の目的そのものが達成されない
(b) 品質の他の側面やコストなどに悪い影響を及ぼす
(c) 関連して実施すべき変更がすべて列挙されずに抜けが起こる
(d) 変更の実施が徹底せず, 実施されなかったり, 一部のみ実施される
(e) 変更指示内容の誤り, 情報伝達の不備, 変更指示の受け取り側の誤解などのため, 意図通りの変更が実施されない

などがある.
P.128 5.品質保証機能 (6) 変更管理

本記事の読者のかたには「何をいまさら」感があるかもですが、一部の分野ではテストの重心がリリース時点での品質の充足から安全な変更に移りつつある現代において、改めてそれぞれの項目に対してどんな施策が打てているかを振り返っておきたいと感じました。

その他グッときた記述

私は早い時期から, 品質管理というものが, およそ目的達成のための筋の良い哲学と優れた方法論を与えていると感じるようになっていた.
P.iv まえがき

標準化は, 改善の基盤のみならず, 実は独創性の基盤でもある.
P.48 品質管理の基本的考え方

改善する=何かを良くするためには現状を正しく認識する必要がある、ということ。
独創性を発揮するための物理的、精神的リソースを生み出すための重要な基盤である、ということ。

形だけの TQM を導入して苦労する例を分析してみると, 問題解決を軽視していることに気づく. (略)二つのことが忘れられているからである. 第一は,方針管理を導入する前に日常管理の仕組みができていなければいけないこと,第二は, 管理を実施していく上での問題解決が基本であることの二つである
P.164 問題解決

Appium に Permission to start activity denied と言われるとき

Activity なので、Androidの話ですね。IDEからAppium経由のテストを実行するとき、コンソールで、"Permission to start activity denied" と怒られることがあります。そんな時は以下を確認。

  • 開発者オプションの "USB経由のアプリを確認" をDisableにしているか?
  • AppiumGUIの方で Wait/Launch Package/Activity のチェックが外れているか?
  • そもそも adb shell am start -n YourPackage/YourActivity として起動できるか?

僕は2番めを引きました。