がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

Dominant-Negative(ドミナント・ネガティブ)とAI開発

「Dominant-Negative(ドミナント・ネガティブ)」っていう言葉があって。
以前にここで取り上げたお題です。
https://gallu.hatenadiary.jp/entry/20101106/p5

生物は面白いもので、ある「重要ななにか(ここではとあるタンパク質)」が「まったく存在しない」場合、それなりにどうにか迂回路とか作っちゃってがんばっちゃうらしいのですが。
これが中途半端に「あるっぽいんだけど実は壊れている」と、「あるっぽい」から迂回路が作られず、でも「っぽいだけだから」役には立たず、で。
結果、全欠けよりもよっぽどたちの悪い「中途半端野郎」が横行する、というような状態になる、んだそうです。

この辺をもうちょっと広げて考えてみると。
「正常"っぽく見える"んだけど実際には動かないから、ぱっと見は"よいっぽい"んだけど、実際には機能しない」的な感じになります。

んで。
ここ最近、この「ドミナント・ネガティブ」という言葉を、特に AI を使った開発の文脈で思い出す場面が増えてきたんですね。

AI の登場で、コードの生成は一気に手軽になったと思う。実際、プログラミングを(さほど)知らない人でも、AI に聞きながらコードが書けたりする。
でも、この"あるように見えるけど実は欠陥がある"、という危うさって、なんか「今まで以上に身近になってないかなぁ?」っていう危惧が、おいちゃん的には気になるところなのでございます。

「ないなら気づける」が、「あるっぽい」と途端に見えづらくなる

実装が"ない"ってのが、まぁ……ないこともない。
未実装なら、まだ気づける。ログが出ない、画面が動かない、エラーメッセージが出る。問題は露骨で、目に入るんだよね……ちゃんとチェックしていれば、だけど。

ただ、「実装がある"っぽい"」だと、これが途端に「気づきにくくなる」と思われる。
見た目は整っているし、コンパイルも通るし、簡単なテストは通る。
ところが、細かい仕様を満たしていない、異常系でおかしな動きをする、なんなら「ちょっと複雑な正常系」でもおかしな動きをする、隅を突くと壊れる、性能が伸びない、などなど。

この手のを「ちょっとしたチェック」で確認しようとしても見つからず、結果、発見が遅れたりはしないだろうか? っていう危惧がある*1

この辺になると。書き方と「そのコードの理解度」次第、にはなるんだけど、状況次第では「結構広域に、なんなら全コードの総見直し」とかになりそうな可能性が、ごくまれに、わずかに、時々、ちょいちょい。
「ちょっとしたコード」ならいいんだけど、「割と大きな規模」のコードでこれをやらかすと、いささか厳しくない?

AI 開発は「ドミナント・ネガティブ化」を促進しがちでは?

AI は、わかりやすいしよいコードをはいてくれる、とは思うんだけど、一方で「それっぽいコード」を出してくるのもうまいと思う。
ぶっちゃけ、自動補完とかでも「うんこんな感じのコードが欲しいんだよね……マテ、そのメソッド、ないのでは?」っての、一度や二度ではない経験がある。

  • クラスや責務の分割が形だけ整っている
  • コメントも丁寧
  • テストも付いてくるし、基本的な項目はテストできている(っぽい)
  • 代表的なユースケースは動く

表面を見るだけなら、「あぁ、できてるじゃん」と判断してしまいやすい感じである。

でも、それは本当に「必要十分」なコード??
境界条件は?
例外パスは?
テストは本当に十分?
性能とスケールの観点で地雷はない?

「それっぽく書かれている」とついチェックも「うん大丈夫そう」ってなってしまいそうになるだけに、「あるっぽい欠陥品」が入り込むリスクは、昔と同レベル以下に納められているんだろうか?

複数のレイヤーで「ドミナント・ネガティブ」化する

んで。おいちゃんが気にしているのは、上述だけではないんだよね。

1. 機能・ロジックの層
代表ケースだけ通り、境界や例外で落ちる。
「正常系だけできている世界」が構築されてしまうと、これが後からの修正を強烈に阻害する。

2. 性能とスケールの層
サンプルデータでは軽い。
本番データ量で、特に運用が始まってからどこかで「急激に重くなる」。
しかしテスト環境で少量のデータなら「ちゃんと動いている」。

3. 保守性の層
ぱっと見は「見目がよい」んだけど、じゃあ修正とか機能追加とかの時に「どこを修正したらよいの?」が不透明になったり、軽微な修正でも「かなりあちこちのファイルを変更」したり、場合によっては「一部変更が漏れたり」。
DRYになっていなかったり、抽象の切り口の問題があったりすると、この辺は運用中に、結構な阻害材料になる。

では、どう対処すべきか?

この辺は今でもきっと議論がつきないところだと思うんだよねぇ。
ただ、まず最低限するべきことはあると思う。

  • 仕様を明示する(ために、仕様を明確化する)

まずはここ。
ここが抜けてたら全部アウトだから。

ただ、これ以降については、いろいろ。
例えば「コードレビューをしっかりする」についても色々と是非があるっぽいし、実際個人的には「是非はともかく、現実問題として"人間が全部コードレビューをする、のは、無理になるだろうなぁ"」って予想している。
個人的には「テストコードをしっかりチェック/レビューする」ほうがよいと思ってる。ただ、これもなんか反証とかあるっぽいんだよねぇ。

なので、ここはまだ「発展途上」ではあるんだと思う。

PDCAを回して、早期検知・早期修正を前提にする

だとすると結局は「早く間違えて早く直す」ほうが現実的なのだと思う。システムにもよるんだけど。
問題の発生自体をゼロにするより、発生したらすぐ検知し、すぐ修正するほうが合理的なケースは多い、っていうか、Webは割とそーゆー文化が強いよね。

仕様・レビュー・テストは必要条件だが、十分条件にはならない。
だからこそ、開発プロセス全体での回転速度が重要になるんじゃないか? って思ってる。


現実問題として「事前に全部のチェックができるか?」っていうと、やっぱり難しいんじゃないか、って思う。
なので「できてるっぽいんだけど実はできてない」ってのは、避けようがないんじゃなかろうか? と。ぶっちゃけ「人間が書いてるコードでもまぁよくある話だよねぇ」な内容ではあるし。

ただ、AIは「ぱっと見の良さ」が非常に洗練されているから、「できてるっぽいんだけど実はできてない」が、より一層深刻になりそうではあるんだよね。

なので。
この辺を「どうするか?」ってのが、AI を使って開発していく上で、一つ大事なんじゃないかなぁ? って思ってる。

ってのをツラツラと考えていた時に、ふと「Dominant-Negative(ドミナント・ネガティブ)」を思い出したので、忘れる前に備忘録。

*1:危惧ってことにしておいて……

治療魔術、回復魔法と治癒魔法

今日は、一般的に治癒魔法とか回復魔法とか呼ばれる類いのものについての正しい分類を理解してもらう。
これらの魔法/魔術は、大まかに3種類に分けられる。
それは「治癒魔法」「治療魔術」「回復魔法」の3種類である。

治癒魔法は、もっともポピュラーで身近な魔法だ。
人体の「治る力」を呼び起こし、増幅させることによって傷や病を癒やす魔法である。これは「魔術、と呼ぶほど詳細な術理を学んでいなくても扱える」ために、治癒魔法、と呼ばれる。
ある程度の怪我や軽症の病などは治せるが、病や毒によっては「その人体の治る力、では勝てない」ので、治らない。
また「本人の"治る力"を使う」ため、体力が極端に低下している場合などに使うと、効かないもしくは致命的な結果が起きることもあるので注意が必要だ。
比較的低いところに限界があることを理解して使う必要があるだろう。
術者のリソースをあまり使わないので、一番ポピュラーではある。

治療魔術は「専門の訓練を受けた術士だけが扱える」魔術だ。
治療魔術の本質は「外部から必要なものを入れたり、患部から(毒や病の元といった)不要なものを取り除いたり、ずれた骨を整形したり、といった「外的要素」を差し込むことで治す」こと、である。
術式としては「治癒魔法を含む」ことが多いが、加えて「外的要素のin/out」があるので、治癒魔法ではどうにもならないようなモノでも癒やせるのが強みだ。
ただしそのためには「見極め(診断)」が必要なため、知識なり検査魔術なりその他「治すべきものを見極めるナニカ」が合わせて必要になる(ので、それも込みで"治療魔術"と言われることが多い)。
なお、四肢損失あたりになると「つなげることはできる」けど「なくしちゃった部位は作り出すのが難しい(本人の細胞から即席で培養、が、ものすごい技術と魔力があれば不可能ではないから王族が莫大なお金払って術式を儀式でまかなって、など大がかりなコストがかかる)。
人を治し癒やすという観点からは強力だが、術士の知識、術の巧みさ、場合によっては外部からの様々なリソースなど色々なものが必要となるため、どうしても高価なものになりがちであろう。

さて「回復魔法」だが、これは上述2つとは趣が異なる。普通に暮らしているのであれば「聞いたこともない」ほうが多いであろう。
術式としては、上述2つとは一線を画す強さがある。回復魔法は「正常(と術者または被術者が認識している状態)に戻す」魔法である。
ここでこの術理の怖さ危険さ奇妙さに気づけるならば、よく学んでいる証拠であろう。
「異常な状態を戻す」とは、つまり「時間の巻き戻し」であったり「事象の否定」であったりする、と考えられている。
術式としてはかなり希有なもので、詳細不明な部分も多い、が、端的に(おそらくは)「人間が持ち得てよい術式ではない」。
「正常に戻す」ので、やっかいなものであっても「特定の必要すらなく」元に戻せる。
死者蘇生が可能(かもしれない)のは、唯一、この術式である。
この術式は本当に「わかっていない」ことが多いのだが。回復魔法は「現世の定め(法)に直接、魔力で介入する方法」であるために、回復"魔法"と呼称されることが多い。
10年単位くらいで、素質のある子供が1人か2人くらいは生まれる、かも? といわれているが、詳細は不明。
また「正常」の定義が不明であり、もし私の懸念が的を射ているのであれば、「術者が、通常とは異なる"正常"を認識した」時に、どのように「回復」するのか? ということを考えると(以下数文が消されている)。


大まかに上述の3系統があるが、バリエーションが少しあるので、そこについても講義をしておこう。

療養/救護/手当魔法
治癒魔法のもうちょっと弱い感じのやつで、ちょっとしたのは子供でも使えたりする魔法である。
子供が「かすり傷を治せる」ことは時々あり、そういったものがこれに属している。
ただ、ちゃんと使うと、後の治癒魔法や治療魔術の助けになるので、きちんとした教育機関で学習させるケースも少なからず存在する。

再生魔術
これは「治療魔術」の特化型で、四肢欠損とかに特化した魔術である。
「部位があれば(多少欠けていても)つなげられる」から「部位が欠損していても、生やすことができる」まで、グラデーションがある。
生やす場合、魔力も触媒も、相応に大量が必要となる。
ただ「ある程度、必要なリソースを軽減する」ような術式もあり、そういったものを持っている事も多い(それでもなお、結構なリソースを必要とするが)。

4Eゲーム、作ってみるお!!

4Xゲーム、好きな方ではあるんだけど、eXterminate(殲滅)だけが、どうにも、あんまりお好まない感じで。
いろいろと考えたりしていたんだけど、ふと「じゃあ最後のXだけ別のものにしちゃえば?」と思って、考えてみた。

Explore(探索)
Expand(拡張)
Exploit(運用/開発)
Exalt(昇華/救済)

「探索して」「拡張して」「開発運用して」までは4Xと一緒。
でも最後は殲滅ではなくて、「世界を良くする」「救う」方向にしてみた。

イメージとしては。
限られた期間(時間)の中で、世界でも地域でも領域でも、とにかく“今ある状況をどこまで改善できるか”を競う感じ。
なんで、4Eゲームとか言ってみたかったり。

頭の中には「馬鹿でかい」イメージが大量に浮かぶんだけど、ちょっと「小さい」ゲームから作ってみようかなぁ、と思うので。

乞うご期待!!

\Random\Engine クラスの generate() メソッド仕様覚え書き

この記事は「外部から色々たたいてとりあえずこんな挙動だった」くらいの内容で、PHPC言語実装とか追いかけてないので、いったん「今この瞬間、うちの環境だとこう動いた」くらいの内容です。
参考なり参照なりされるさいは、用法用量を守って適切に参照してください。

本題。

いや、元々は以下のようなニーズがありまして。

  • \Random\Randomizer の getInt()を
  • ゲームの文脈で使いたい
  • で、テスト用に「乱数の生成部分」をDI等で差し替えたい

なのでまぁ、コンストラクタ的に「engineをこっちの任意のものに差し替えればよいだろう」くらいの感じでした。
https://www.php.net/manual/ja/random-randomizer.construct.php

なお、その後に使うのは getInt()「だけ」を、いったん、想定しています。
https://www.php.net/manual/ja/random-randomizer.getint.php

んで。
いや「Randomizerを継承したクラスでgetInt()を上書き」できればそれはそれでよかったんですが、いかんせん、final classなんですよね。
なので、差し込むエンジンのほうを触るしかないかなぁ、と。

Random\Engine インターフェイス、generate() メソッドだけなんですよね。
https://www.php.net/manual/ja/class.random-engine.php

ただまぁ、大体必要なことは書いてあります(はじめこのページ見てなくて結構体当たりでかなり馬力な調査をして、この記事書くのに確認したら大体必要なことが書いてあった、的な裏舞台があったことはナイショのお話です)。
https://www.php.net/manual/ja/random-engine.generate.php

結論、必要な箇所はここ。

整数の値をネイティブで操作するアルゴリズムは、 たとえば pack() 関数に P フォーマットコードを指定するなどして、リトルエンディアンで整数値を返すべきです。

ただ、確認すると

  • うちの環境は、いわゆる64bit系の環境なんで、フルビット立っても、8bytes(64bit)
  • 一方で、このメソッド、どうやら16bytes文字列を返すっぽい(コードで確認したらうちの環境だとそうだった)

ってことがわかったので。リトルエンディアンなんで、8~15バイト目に適当なpaddingを突っ込んでおります。

んでまぁ、とっととコードを。

<?php
declare(strict_types=1);

class MockRandomEngine implements \Random\Engine
{
    /** @var list<int> 数値配列 */
    readonly private array $byteValues;

    private int $position = 0;

    /**
     * @param list<int> $byteValues 数値配列(キーは array_values で詰め直す)
     */
    public function __construct(array $byteValues = [])
    {
        $this->byteValues = array_values($byteValues);
        $this->position = 0;
    }

    public function generate(): string
    {
        // byteValuesが空なら0、そうでなければ現在位置の値を使用
        if ($this->byteValues === []) {
            $value = 0;
        } else {
            $count = count($this->byteValues);
            $value = $this->byteValues[$this->position % $count];
            // 次の位置に進める
            $this->position = ($this->position + 1) % $count;
        }

        // リトルエンディアン64bit符号付き整数(8バイト)に変換
        $bytes = '';
        // 符号付き64bit整数をリトルエンディアンでバイト列化
        for ($i = 0; $i < 8; $i++) {
            $bytes .= chr(($value >> ($i * 8)) & 0xFF);
        }

        // 16バイト返す必要があるので、残り8バイトは0で埋める
        return $bytes . str_repeat("\x00", 8);
    }
}

これで「コンストラクタの時に渡した数値配列」を繰り返し出力するようなエンジンを作ることができます。
使用例は、こんな感じ。

$engine = new MockRandomEngine([1, 255, 512, 1024]);
// $engine = new MockRandomEngine();
$randomizer = new \Random\Randomizer($engine);

for ($i = 0; $i < 10; $i++) {
    $value = $randomizer->getInt(0, PHP_INT_MAX);
    var_dump($value);
}

んで、注意点。
getIntの第一引数が「エンジンから帰ってくる値に単純に加算される」。
なので、ゲームを想定しているからなんだけど、例えば以下。

$engine = new MockRandomEngine([1, 2, 3, 4]);
$randomizer = new \Random\Randomizer($engine);

for ($i = 0; $i < 10; $i++) {
    $value = $randomizer->getInt(1, 6);
    var_dump($value);
}

を実行すると、こうなる。

int(2)
int(3)
int(4)
int(5)
int(2)
int(3)
int(4)
int(5)
int(2)
int(3)

あとまぁこっちは当然なんだけど、maxより大きな値をエンジンが返すと、エンジンが生成した整数値を範囲幅で割った剰余を使って指定範囲にマッピングしている感じですね。
なので

$engine = new MockRandomEngine([1, 2, 3, 4]);
$randomizer = new \Random\Randomizer($engine);

for ($i = 0; $i < 10; $i++) {
    $value = $randomizer->getInt(1, 3);
    var_dump($value);
}

だと

int(2)
int(3)
int(1)
int(2)
int(2)
int(3)
int(1)
int(2)
int(2)
int(3)

ってなる。

この辺に留意すれば、一応「エンジンで、乱数のテスト用の差し込み」は作れる感じかなぁ。
(とはいえ面倒なんで、別の方法で作るけど……ってのは次のブログで)。

いろいろ調べたり試行錯誤したりして、知識が霧散するのももったいないんで、備忘録的に。

秘密鍵と公開鍵(鍵ペア)の作成の仕方: 半分くらいはうちの生徒さんに向けて

秘密鍵と公開鍵(鍵ペア)の作成の仕方」は、大切なんですが忘れやすいものでもあるので、簡単にメモ程度に記録を残しておきます。
いったんWindowsでの操作しか書いていないので、Macの人は別途また。

まず、PuTTY Key Generatorを立ち上げます。
PuTTYgenって検索すると出てくるので、探すとよいでしょう。

あるいは、windowボタンからアプリの一覧経由で探してみてもよいです。

選択すると、アプリが立ち上がります。

下の「Parameters」を選択します。
選ぶのは「EdDSA」。

次に、Generateボタンを押して鍵の作成を開始します。

ボタンをクリックすると、こんな風になります。

上の画像水色のあたりでマウスカーソルを動かすと、上の緑色のバー(赤い印をつけているところ)が、だんだん右に進んでいきます。

グリグリとマウスを動かして、緑色のバーが完全に右端に到達すると、こんな画面になって、これで「鍵の作成完了」となります。

秘密鍵の保存

まず「key passphrase」と「Confirm passphrase」に、「秘密鍵を守るためのパスワード(パスフレーズ)」を入れます。
秘密鍵を守る最後の砦なのである程度の強度はほしいけど、忘れたら「鍵の作り直し」しか方法がないので、加減をしながら。

パスフレーズを入れたら、「Save private key」で秘密鍵を保存します。

ファイル名は任意とお好みで。保存場所もどこでもよいけど、後の作業を考えると、デスクトップに保存するのは割と楽なのでおすすめします。

続いて公開鍵の保持

公開鍵は、ここ。

全文をコピペして、どこかテキストファイルにでも保存しておくと便利です。
「右クリック → すべて選択」で公開鍵を全部選択できるので、よかったら覚えておいてください。

ここまでで「秘密鍵」と「公開鍵」、つまり 鍵ペア が作成できたことになるので。
秘密鍵は大切に保存。
公開鍵は、渡すべき人に渡してください。公開鍵なんで、メールとかでの送付も普通にOKです。

以下うちの専門学校の学生に向けて

秘密鍵は「USBメモリ」に保存しておくのをおすすめします。次点は「TeamsのOneDriveのマイファイル」の中でもよい、かもしれません(あまりおすすめはしませんが)。
USBメモリなりTeamsなりに移動したら、デスクトップのファイルは削除しておきましょう。

公開鍵は、「自分のLinuxアカウント名」を添えて、おいちゃんに送ってください。送る方法は「Teamsの、授業のチームのチャット」がベストです。

なおLinuxアカウント名ですが
・以前に作ったアカウント名(なので、忘れないように)
・初日のみ、以下のルールに従って任意のアカウント名
 → 使用可能なのは「アルファベット小文字」、二文字目以降は数字もOK、記号は_(アンダースコア)のみ(スペース(空白文字)を含めないこと)。文字数は最大で32文字まで
 → 「1文字目の数字」「英大文字」「アンダースコア以外の記号」は使うことがで>きないので注意
 → 普段使いのハンドルがあったらそれを、なかったら「自分のフルネーム」が、他の人との重複しにくいし忘れにくいので安全

こんな感じで、自分のアカウント名を決めてください。

SQLite 雑速習

ざっくりSQLiteを速習してみました(業務では全く、個人的にも全く触ってなかったので)。 ほぼ覚え書き程度なので、ちゃんとした内容はちゃんとしたところで調べてください(笑

確認

sqlite3 -version

なかったらインストール

sudo dnf install sqlite

database領域作成&接続

sqlite3 test.db

DDL流し込み

てけとうに

## テーブルの確認
.tables
## スキーマ(テーブル情報)の表示(テーブル名省略すると、全部出る?)
.schema [テーブル名]

## databaseの確認(やっとくとPHPの時のDSN作るの楽)
.databases

## 列名を表示
.header on
## モード変更して見やすく
.mode column
## こっちは \G みたいな感じ
.mode line

## SQLファイルを読み込んで実行
.read ファイル.sql

## バックアップ
.backup backup.YYMMDD.db
## リストア
.restore backup.YYMMDD.db

## 抜ける( ctrl-C でもいけるけど)
.q

PHPで雑接続

<?php

$dsn = 'sqlite:/home/gallu/test/test.db';
$pdo = new PDO($dsn);
var_dump($pdo);

型 覚え書き

varcharとか指定できるらしいのですが内部的には以下にそろえられちゃうようです。

NULL NULL型
REAL    浮動小数点
INTEGER 整数
TEXT    任意の文字列
BLOB    任意のバイナリデータ

phinx確認

composer require robmorgan/phinx
vendor/bin/phinx init
mkdir -p db/migrations db/seeds

phinx.php

return
[
    'paths' => [
        'migrations' => '%%PHINX_CONFIG_DIR%%/db/migrations',
        'seeds' => '%%PHINX_CONFIG_DIR%%/db/seeds'
    ],
    'environments' => [
        'default_migration_table' => 'phinxlog',
        'default_environment' => 'development',
        'development' => [
            'adapter' => 'sqlite',
            'name' => '/home/gallu/test/test.db',
        ],
    ],
    'version_order' => 'creation'
];

って書いたら /home/gallu/test/test.db.sqlite3 ってなった。拡張子 .sqlite3 がデフォでつくぽい。 後は大体一緒ぽ。 多分汎用的に考えて

return
[
    'paths' => [
        'migrations' => '%%PHINX_CONFIG_DIR%%/db/migrations',
        'seeds' => '%%PHINX_CONFIG_DIR%%/db/seeds'
    ],
    'environments' => [
        'default_migration_table' => 'phinxlog',
        'default_environment' => 'development',
        'development' => [
            'adapter' => 'sqlite',
            'name' => __DIR__ . '/test.db',
        ],
    ],
    'version_order' => 'creation'
];

がいいんだろうなぁ直下に置くかどうかはともかくとして __DIR__ 使えば、どこにおいても問題ないし。

マイグレーションの確認。

php vendor/bin/phinx create TestTable

    public function change(): void
    {
        $table = $this->table('test');
        $table->addColumn('name', 'string')
            ->addColumn('str', 'string', ['limit' => 128])
            ->addColumn('str2', 'varbinary', ['limit' => 128])
            ->addColumn('num', 'integer', ['comment' => 'コメント'])
            ->addColumn('num2', 'biginteger')
            ->addColumn('created_at', 'datetime')
            ->create();
    }

php vendor/bin/phinx migrate --dry-run

からの

php vendor/bin/phinx migrate

ですんなり。 うん大体把握。なんかでチャンスあったら使ってみるかねぇ?

「お客様は嘘をつく」「XY問題」

がっつり備忘録。
https://gallu.hatenadiary.jp/entry/20090317/p2 でも書きましたが。
お客様は「自分が必要なもの」を十全にうまく言葉にできる、とは限らないので、その辺をちょっと刺激的な言い回しにして、うちの子たちには「お客様は嘘をつく」なんて言い方をしていますが。

最近「XY問題 https://ja.wikipedia.org/wiki/XY%E5%95%8F%E9%A1%8C 」なる言葉を見つけて、ジャストミート言いたいことが書いてあったので。
んで、「XY問題」って単語はまぁ確実に忘れる事が予想されるので(笑)、ここにメモしておきます。

いやまぁ「Y(二次的な課題を解決する方法)」ですら、的を射てない事が多いですしねぇ……っつかお客様がその辺で「確実に的を射る発言ができる」ようであればこちら側の何割かがおまんま食い上げでございます(笑
なので、その辺は正直、こちら側が「適切にヒアリングを重ねて」ニーズを把握していかなきゃ、とは思うんですよねぇ、っての、込み込みで。