技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

KPTが出てこないケース

カイゼン・ジャーニー読んでたら、ふと思い出したので。

カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで

カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで

Keep Problem Try の頭文字で、スプリントの最後に、「こう言う事良かったね続けようか」「こういう所問題だったよね」「なら問題解決にこう言う事やってみようか」を行うのが agile では定例。
これをチームによって期間はまちまちだけど、こちらは 2 週間に1回やった。

で、問題があったプロジェクトは、これが全く回らなかった。
どういうことかというと、problem がその場限りの問題解決になってしまったのだ。

そもそもイテレーションの運用内部「このプラクティス無駄だよね、こうしてみない?」とかそもそもプロジェクト運用に関わる問題が出てきてくれる方が望ましい。
でないと取り組みが改しないのだ。

これが 2-4 イテレーション位なら疑問にお思わないのだけど、結局最終スプリントでもそんなだった。
問題がなかった?というのは考えにくい、大なり小なり問題はあったハズだ。

何が問題だったのだろう?そう考えた

心理安全が確保されてない?

これはあったかもしれない。
そのプロジェクトは遠隔地 2 拠点での開発だった。
当然情報の粒度や格差の問題は大なり小なり出る。

何より、相手の姿が直接的に見えない。
もっと言うと気づいていないだけで、各拠点が排他的な村になっていた可能性はある。

Agile のプラクティスには大体鉄則がある。
同じ場所でやることアジャイル・サムライにはあり、実にその通りと実感する。

そうでなくとも、そうした事を理解したキーマンを両拠点に常駐させておくべきだったのかもしれない。

そもそもプロセスに無頓着

元々 SIer のニアショア拠点。言われた事への作業に慣れ切ってる。
果たしてその状況で開発プロセス自体が改善できただろうか?

そりゃ温度感が違うのにプロセスを見直す発想を求めるのは酷というものだ

後からなら何とでも言えるケースだが、こうした拠点でアジャイルするなら、有識者を常駐させ、好きに言える土壌を作らなければいかなかったのかもしれない。

改善の糸口が分からない

problem を出すときのコツは、自分がやってて詰まらないと思った作業、ド嵌りした事象だ。
ド嵌り事象は情報共有で済む場合もあれば、プロセスを変える事でそのポイントをそもそも回避できる場合がある。

何にしても言ってみる事が重要だったりする。

作業中とは面白いもので、上記のような事があっても喉元過ぎると熱さを忘れるものだ。
良くアプリが思ったように動かなくてイライラしているユーザも、どうすれば良いか分かった瞬間から問題に思わなくなる事象と一緒だ。
(いやそれユーザビリティが悪いんだからね?(汗)

SI 現場でそういうシーンは結構ある。

  • テストを手動で回してテストデータでひーひー言う
    テストそのものを自動化する運用とするか、テストデータ生成を自動化する
  • リリース作業で、手続きにチェックを入れながら…
    いや CI で自動化できる
  • 仕様書を探してディレクトリを右往左往
    検索エンジン入れましょう、運用的にドキュメントの配置をルール化しよう…etc

みたいなことが往々にしてある。
これらを解決可能だと思ってないか、過小評価して(だから改善するまでもないと思って)いるケースが多い。

どうすればわかるのだろう?
個々人が、30分以上同じ作業をしたらとりあえず紙に書いてみるという、要するに時間の使い方の見える化だろうと思う。

最急降下法を使用して、近似線を引いてみる

やっていることは前回とほぼ同様。
ただしこちらでは偏微分を使って複数のパラメータを計算している。

white-azalea.hatenablog.jp

目標とすべき線が

def actual(x):
    """目指すべき線"""
    return 0.6 * x + 4

で、学習データセットを以下の様に設定してみる。

data_set = [(x, actual(x) + random.randint(-5, 5)) for x in range(-100, 100)]

緑が学習データで、青線が理想の線。
ばらけ方は悪いけど、この辺がテストデータ

f:id:white-azalea:20190218204818p:plain

そして求めるべきパラメータは、前後変化するので引数で受け取る様にして外だしに。

# weight のパラメータを使って actual 同様の計算をする
def func(x, weight):
    return weight[0] * x + weight[1]

あとのやることは対して変わらない。
微分関数を用意。

def numerical_gradient(f, weight):
    """偏微分式にアップデート"""
    h = 1e-4
    grad = []

    for i in range(0, len(weight)):
        cpy1 = weight[:]
        cpy2 = weight[:]
        cpy1[i] = cpy1[i] + h
        cpy2[i] = cpy2[i] - h
        fhx1 = f(cpy1)
        fhx2 = f(cpy2)
        grad.append((fhx1 - fhx2) / (2 * h))
    return grad

ただし、今回は重み付けパラメータの値を個別に微分し、微分値を応答に含む様にしている。
偏微分て要するに他のパラメータを全て定数とみなして微分することなので、やっている論理は一緒。

損失関数を用意して、その損失関数に不足するパラメータを設定する。

def loss(func, data, actual, weight):
    calc = func(data, weight)
    return (actual - calc)**2

今回は 0 に近づけるのではなくて、微妙にブレた各数字データに近づける。

lern = 0.00001
weight = [-2, 3]  # 最初の重みは適当
for n in range(1000):
    for data in data_set:
        x, y = data
        loss_func = lambda w: loss(func, x, y, w)
        grads = numerical_gradient(loss_func, weight)
        weight = [weight[0] - grads[0] * lern, weight[1] - grads[1] * lern]

初期の weight は明らかに伸びる向きが間違ってる。
これは補正がかかることを確認するためにわざと行なっている。

その上で赤線プロットするとこのようになる。
(黄色は学習前で、赤は学習後。きちんと学習しているようである)

f:id:white-azalea:20190218205933p:plain

ここでもう一声。
損失関数と呼んではいるが、これは目的関数とも言えるので、その原義(下記)に従って、損失計算を作り直してみる。

f:id:white-azalea:20190218220922p:plain

※ y(i) はデータはデータの y 軸の値。fθは今回で言う所の func(?, weight)の意味で、θ=weight 値。

lern = 0.000002
weight = [-2, 3]  # 最初の重みは適当
for n in range(5000):
    def error(weight):
        all_err = 0.0
        for data in data_set:
            x, actual_y = data
            all_err += (actual_y - func(x, weight))**2
        return all_err / 2.0
    grads = numerical_gradient(error, weight)
    weight = [weight[0] - grads[0] * lern, weight[1] - grads[1] * lern]

f:id:white-azalea:20190218221522p:plain

ループなどは一部弄ったが、ループは明らかに少ない回数でいい精度の数字が出てきた。

Trailheadが地味に楽しい件

SalesForce 社のオンライン学習ぷらっとフォーム。

trailhead.salesforce.com

ここの内容が多岐に渡ってて、だらだらと学習を続けるのには面白い点。
そして、積み本が溜まっていく(汗

今月はとある活動もあってあまり更新できてなかったけど、そろそろこっちの更新もしてこうかと思う。
とりあえず以下の本も興味本位で買ったので、積まずに読まねば

カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで

カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで

この辺は読み物系なので、実践系と違って電車で読める(汗
プログランムとかアルゴリズムの類は見たら書きたくなるので、PC の前以外でなかなか読まないのが難点(そしてそっちの方が好きだという問題)

読み終わったら感想とか書きたい。

微分を使ってパラメータを求める(最急降下法or勾配降下法)

お題は、ある不明関数 f にっついて、t という値にもっとも近くパラメータを見つける。
ということで、ちょこちょことやってみた。

計算したい関数はこれで、もっとも 0 に近くなる x を求めたい。
このくらいならふつーに計算すればできそうなものだが、あえてプログラム的に。

def func(x: float) -> float:
    """計算対象の関数

    Arguments:
        x {float} -- 引数

    Returns:
        float -- 計算結果
    """

    return 2 * (x ** 2) + 2 * x + 1

ある値 x に対する誤差の計算を以下の様にかける。

def loss(f: func, x: float, t: float) -> float:
    """損失関数

    Arguments:
        f {func} -- 値を求める関数
        x {float} -- 確認する x 値
        t {float} -- 正しい値

    Returns:
        float -- 誤差値
    """
    y = f(x)
    return (t - y)**2

この時、関数は前述の func で、求めたい t は 0.0 固定でいいので

loss_func = lambda x: loss(func, x, 0.0)

こんな感じにしておく。
ここで、この勾配を求めるのに、微分をしてみる。

微分は、グラフ上の接線、ないしは接点であるとも言え、その定義は極限で 0 に収束させる時の値だったはず。
なら処理的にはこんな風に書けるはず。

def numerical_gradient(f: func, x_value: float):
    h = 1e-4
    grad = 0.0

    x1 = x_value + h
    x2 = x_value - h

    fhx1 = f(x1)
    fhx2 = f(x2)

    grad = (fhx1 - fhx2) / (2*h)
    return grad

その上で、800 回ほど誤差計算とパラメータの移動をさせてみた

loop = 800
lern = 0.001
current_x = 3.5  # 開始値
log = []


for n in range(loop):
    # 微分して
    gradient = numerical_gradient(loss_func, current_x)
    # 誤差を元にあるべき x に近づける
    current_x += gradient * lern
    log.append(current_x)

学習係数をかなり小さくしてやらないと、プラスとマイナスでやたらと振れた挙句オーバーフローを起こすので注意

f:id:white-azalea:20190206172104p:plain

プラスからでもマイナスからでも、最終的に x=-0.5 に収束する。
ニューラルネットワークの学習計算から論理を切り出してみたのだが、なんとなく良さそう。

ゼロDeep4章のニューラルネットワーク学習

論理としては理解できたのでメモ。

  1. 教師データを食わせたとき、その演算結果(行列、インデックスがそれぞれ分類を示す)と、出てきてほしい結果(これまた行列)の誤差を行列で取得する関数を用意する。(損失関数)
  2. この損失関数と、現在のニューラルネットワークの重み、バイアスをパラメータに微分して、パラメータのどの方向に変動させるべきかを見る。 最急降下法というようだ。
  3. 出てきた誤差を、学習係数を踏まえて実際にパラメータへ入力する。

という流れで、ニューラルネットワークの各階層をの「重み」「バイアス」を弄っていく。
というのがどうも学習の論理らしい。

そこまでは腑に落ちた。
で、実際やってみたところ、サンプルの時点で処理オワンネ(汗

StudyDocs/two_layoput_net.py at master · Sunao-Yoshii/StudyDocs · GitHub

それも当然で、サンプルだけで見ると、ニューロンの数が

  • 入力層: 784
  • 出力層: 100

このニューロン1個あたりで「重みとバイアスで2回」x ニューラルネット判定(入力層と、出力層で2回) x 微分(前後計算で 2 回)で 8 回計算する羽目に…。
しかも行列計算、浮動小数店計算。

ベンチマークもびっくりの計算量ですよ。
しかも論理を説明するため、処理時間を考慮しないシングルスレッド…そりゃ無理でんがな

Checkstyle の Metrics 近辺の内容を日本語で分かり易く

checkstyle.sourceforge.net

個人的にチェックスタイルでもっとも素晴らしいチェックの項目群だと思うのだけど、これで引っかかると、意味が理解できなくて直すの止める人とか、「チェックが開発の邪魔だ」とか言ってくる人が多いので、なるだけ分かり易くメモしておく。

BooleanExpressionComplexity

要するに && とか || を繋げすぎた条件式なんてバグるんだから書くんじゃないというチェック。
この数値は、条件結合数( ||&& の数 )で単純増加する。

個人的にここの 3 位が好み。
人間の脳みそは 同時に 5 個位しか処理できない そうですよ。

パラメータ

名称 意味 初期値
max 最大で許容する複雑度の数字 3
tokens チェックする演算子 && & || | ^

ClassDataAbstractionCoupling

中で使用(new とか)しているクラス数チェック。

何より、論理クラスをたくさん import しておいて、見通しが効くのかって問題がある。
これで何がわかるかというと、クラス保守のしやすさ(当たり前だけど、たくさん依存してるクラスなんてテスト書けないぞ)。
もっというと、数字が大きい=そのクラス色々やりすぎてないか?設計粒度おかしくない?って話

パラメータ

名称 意味 初期値
max 最大で許容するクラス数 7
excludedClasses チェック対象外クラス(こいつらは論理ロジックではないので、無視する) HashMap, ArrayList, String, float, TreeSet, List, Boolean, Void, Override, Short, IllegalArgumentException, UnsupportedOperationException, HashSet, void, Character, IndexOutOfBoundsException, byte, double, Double, LinkedList, Float, Byte, SortedMap, Long, Throwable, Object, Class, Map, IllegalStateException, Set, StringBuilder, SuppressWarnings, SortedSet, long, RuntimeException, Deprecated, NullPointerException, Queue, SecurityException, FunctionalInterface, TreeMap, Deque, int, Exception, Integer, SafeVarargs, StringBuffer, boolean, char, short, ArrayIndexOutOfBoundsException
excludeClassesRegexps チェック対象外クラスの正規表現。Voクラスなんかの設定をしておくと吉 ^$
excludedPackages チェック対象外パッケージ。ユーティリティパッケージでも指定しておくと吉 {}

ClassFanOutComplexity

参照しているクラス数。

ClassDataAbstractionCoupling に似ているが、こっちはファイル単位で参照してるクラス数のカウント。
あくまで参照のカウントなので、中でインスタンス化してるかは問題ではなくて、メソッドの返り値を使ったとかそういうレベルでもカウントされる。

ClassDataAbstractionCoupling に比べると数字が上がり易くなる傾向はあるが、この値が大きいということは、把握すべきクラス数が多いことを意味してる。
あんまし大きいと、テストコードが書きにくくなる。

個人的にもこれはデフォルトくらいでよい

パラメータ

名称 意味 初期値
max 最大で許容するクラス数 20
excludedClasses チェック対象外クラス(こいつらは論理ロジックではないので、無視する) HashMap, ArrayList, String, float, TreeSet, List, Boolean, Void, Override, Short, IllegalArgumentException, UnsupportedOperationException, HashSet, void, Character, IndexOutOfBoundsException, byte, double, Double, LinkedList, Float, Byte, SortedMap, Long, Throwable, Object, Class, Map, IllegalStateException, Set, StringBuilder, SuppressWarnings, SortedSet, long, RuntimeException, Deprecated, NullPointerException, Queue, SecurityException, FunctionalInterface, TreeMap, Deque, int, Exception, Integer, SafeVarargs, StringBuffer, boolean, char, short, ArrayIndexOutOfBoundsException
excludeClassesRegexps チェック対象外クラスの正規表現。Voクラスなんかの設定をしておくと吉 ^$
excludedPackages チェック対象外パッケージ。ユーティリティパッケージでも指定しておくと吉 {}

CyclomaticComplexity

循環的複雑度とよく言われる。

10 超えたら、汚すぎて読めないっていって、レビュー突っ返していいレベル(だと個人的に思ってる)。
関数型プログラミングやってる連中だと、これで 5 行くとか、よほどの理由があるかさもなくばバカかと思われる。

if とか while とかを使うことで発生する、処理分岐数の概算値。
具体的には boolean 式 1 個単位でカウントしてる

公式曰く「テストコードを書く前提なら、1-4 に抑えたい。5-7 ならかろうじてテストできるんじゃない?8-10 も行ったらリファクタを検討すべき。」

著作権法はプログラムにするとバグりやすい って話の中で、ドワンゴ CTO の人が、以下のように言及してる。

循環的複雑度が75を超えるとバグの混入確率は98%、「いかなる変更も誤修正を生む」状態になる

個人的見解なら 5 超えたらテストコード書きたくない時点でクソコードだと断じたい。

パラメータ

名称 意味 初期値
max 最大許容する分岐数 10
switchBlockAsSingleDecisionPoint switch 分岐はcase含めて 1 とカウントするか?(false では case ごとにカウント) false
tokens チェック項目 while do for if switch case catch ?(三項演算子) && ||

JavaNCSS

コメント化されてないソースのステートメント(処理)数カウンタ。

公式曰く:

理論的根拠:大きすぎるメソッドやクラスは読みにくく、維持するのに費用がかかります。 NCSSの数値が大きいということは、メソッドやクラスが、より小さな単位に分解されるべき責任や機能を多すぎることを意味します。

個人的には methodMaximum = 20, classMaximum = 500 位の設定がテストしやすいのでオススメ。
関数型言語やってる人なら多分この位でいけるはず。

パラメータ

名称 意味 初期値
methodMaximum 1メソッドあたりの最大ステートメント 50
classMaximum 1クラスあたりの最大ステートメント 1500
fileMaximum 1 ファイルあたりのステートメント 2000

NPathComplexity

Cyclomatic complexity(循環的複雑度)に近しい計算。
ただし、こちらの数字はネストすると単純カウントではなくて、条件分岐なら条件式の複雑性、三項演算は +2 補正とか、もう少し厳密に計算されれる。

1988年のAT&Tベル研究所でのいくつかの情報源の経験的な数ベース

ただ、「やりすぎてロジックの明瞭性を損なうなよ」とは checkstyle 公式の言。
個人的には 100 は超えたくない。

名称 意味 初期値
max NPathの最大許容値 200

ということで、主要なものの説明。

今日やった事メモ

何がしたいのかというと、log4javascript を試したかっただけ。
どんなリクエストが飛ぶかもよくわからなかったので、PHP でざっくりキャプチャするかーって事で、まずはPHP用意。

qiita.com

Docker のおかげで世の中楽になりました。

まずはダウンロード

log4javascript.org

ってもわかりやくすダウンロードリンクがあるのでホイ。

簡単に使ってみる

ダウントードして、以下のような配置をしてみた。

  • log4javascript.js
  • index.html
  • example.js

index は特に何もしてなくて、二つの javascript を取り込むだけ。

<script type="text/javascript" src="/log4javascript.js"></script>
<script type="text/javascript" src="/example.js"></script>

そして、example.js は

var log = log4javascript.getDefaultLogger();
log.info("Example info");

アクセスしてみると…なかなかリッチな何かが出てきたw

f:id:white-azalea:20190127212701p:plain
log4js

ドキュメントのチュートリアルを見てもわかるが、trace, debug, info, warn, error があるらしい。
しっかし Javascript コンソールに出るだけかと思ったよ…

エラーコードも

try {
  throw new Error("Faking something going wrong!");
} catch (e) {
  log.error("An error occurred", e);
}

この通り

f:id:white-azalea:20190127213135p:plain

AjaxAppender

なんでも、ログ情報を非同期にサーバに送るアペンダがあるので、早速試してみた。

var log = log4javascript.getDefaultLogger();
var ajaxAppender = new log4javascript.AjaxAppender('http://localhost/log.php');

log.addAppender(ajaxAppender);
log.info("Example info");

すると、chrome には以下の様な通信が発生していた。

f:id:white-azalea:20190127213757p:plain

f:id:white-azalea:20190127213920p:plain

なるほど、普通にやると HTTP POST でフォームパラメータで送信されるようだ。
因みに、ログのサブウインドウはやはり表示された。

このウィンドウが邪魔な時は、デフォルトのアペンダを丸ごとクリアしてしまえば良さそう。

var log = log4javascript.getDefaultLogger();
var ajaxAppender = new log4javascript.AjaxAppender('http://localhost/log.php');

// remove default appender
log.removeAllAppenders();
// set ajaxAppender only
log.addAppender(ajaxAppender);
log.info("Example info");

尚、アペンダの設定を見ればわかるのだけど、log4j で properties とかに書いてたものは軒並みメソッドとして存在しているらしい。
なので、共通的な設定をしてアプリケーション全体に噛ませるなら、ロガージェネレータ関数でも用意するしかなさげ。

log4javascript 1.4 manual

デフォルトでは、非同期にポンポンログを投げる設定だけど、通常時はログを投げずに unload イベント発生時にまとめて投げる設定なんかもあったので覚えておくと良さそう。
なんにしても送信設定周りはコイツのお仕事。

続きを読む