IT系フリーランスの日記

大まかにIT系と呼ばれる仕事を自営業として営んでいる者の日記です

シナリオプランニングについて

今年に入ってから、個々人が関心を寄せ始めたのがいつかは別として、
新型コロナウイルスSARS-Cov-2)の感染症であるCOVID-19(新型コロナウイルス感染症)の話題で引っ切りなしだ。
未知のウイルスである事も手伝い、様々なデマが飛び交う事態にもなった。
次第にウイルスの性質が明らかになりつつあるが、現実世界でこのウイルスがどのように人々の生活に影響しうるかはなお未知数である。
1月の下旬か2月の上旬辺りであれば、放っておけばその内終息するだろうという楽観的な見方もあった。
迅速検査キットの早期開発・普及や、有効で安全な治療薬・ワクチンの開発等にも大きな期待が寄せられた。
だが、現時点で有効に機能する感染拡大の防止策は未だに外出を最低限に控えるという事に留まる。
当時は、中国共産党政府が感染の封じ込めに強権を振るうことへの批判も多かったが、
最近では感染拡大を封じ込めるためには一定の強制力も必要だという事を認ざるを得ない状況にある。

現在までの所、今年に入ってからの4ヶ月間人々の予測は目一杯悲観的な方向へと裏切られつつある。
今後、安全で有効な治療薬の開発を1から着手するにしても相当な時間がかかるだろう。
それまでに感染が終息してしまえば努力が水泡に帰してしまう。そう考える製薬会社の多くは新薬の創薬には否定的だ。

また、有効なワクチンの開発にもいくつもの疑問の声がある。このウイルスに対して本当に免疫となる抗体が存在するのか?
ワクチンで一時的に免疫を得ても数ヶ月から半年程度の効果しか無い可能性はないか?
などである。

これまで人類が経験してきたあらゆる災厄のうち、これほどタイミングの悪い時期にこれほどたちの悪い疫病が流行ったというのは、
災難を通り越して皮肉でさえある。

しかし、このような疫病の流行を予測する人達がいなかったのかといえばそうではない。

認証サーバーの負荷テスト再び

あの後、認証サーバーの負荷テストがなぜリクエストの頻度が落ちた後もエラー率の高いまま推移し、回復しなかったのかサーバーのログを見てみました。
すると、認証サーバーに登録されたアカウント番号が既にトークンを発行しており、そのトークンを認証サーバーが所持している場合は既存のトークンを削除した上で、
新たなトークンを発行するようなシステムだったのですが、アクセスが集中することでトークンの削除がうまくいかなくなると、それ以降の処理が全てエラーになってしまっていることが発覚しました。
具体的に、Djangoトークン発行の箇所だけを抜き出してコードを以下に示します(バグがあったものです)。

@staticmethod
    def create(user: UserAccount):
        # ユーザの既存のトークンを取得
        if UserToken.objects.filter(account_no=user.account_no).exists():
            # トークンが既に存在している場合は削除する
            UserToken.objects.get(account_no=user.account_no).delete()
        # トークン生成(メールアドレス + パスワード + システム日付のハッシュ値とする)
        dt = timezone.now()
        original_str = str(user.account_no) + dt.strftime('%Y%m%d%H%M%S%f')
        hash_str = hashlib.sha1(original_str.encode('utf-8')).hexdigest()
        # トークンをデータベースに追加
        token = UserToken.objects.create(
            account_no=user.account_no,
            token=hash_str,
            access_datetime=dt)
        return token

この、

        if UserToken.objects.filter(account_no=user.account_no).exists():
            # トークンが既に存在している場合は削除する
            UserToken.objects.get(account_no=user.account_no).delete()

コードの部分だけで、重複したトークンを保存しないようにしていたわけですが、トラフィックが増えてきて負荷が掛かると、ある時点で削除に失敗してしまい、
次からは削除しようにも、複数のデータが存在する事を示すエラーを出力して500 Internal Server Errorとなっていました。

この部分を次のように置き換えることで、トラフィックが増えて負荷が上がっても、エラーを起こすことなく最後まで負荷テストを終えることが出来るようになりました。

        # ユーザの既存のトークンを取得
        if UserToken.objects.filter(account_no=self.account_no).exists():
            # トークンが既に存在している場合は削除する
            UserToken.objects.filter(account_no=self.account_no).delete()
        # トークン生成(メールアドレス + パスワード + システム日付のハッシュ値とする)
        dt = timezone.now()
        original_str = str(self.account_no) + dt.strftime('%Y%m%d%H%M%S%f')
        # utf-8でエンコード
        hash_str = hashlib.sha1(original_str.encode('utf-8')).hexdigest() 
        # トークンをデータベースに追加
        token = UserToken.objects.create(
            account_no=str(self.account_no),
            token=hash_str,
            access_datetime=dt)
        try:
            user_account_update = UserAccount.objects.get(account_no=self.account_no)
            user_account_update.update_access_datetime()
        except():
            print("トークンを発行したユーザーの最終アクセス日時をアップデートできませんでした。")
        return token
</span>

負荷テストの結果は以下の通りです。
f:id:tyusuke88:20191116214032p:plain

フレームワークを使わずにPythonでニューラルネットワークを作る。

以前、Udemyのゼロから作るニューラルネットワークという講座の最終課題として、PythonとNumpy、Pandasを使用して、
2013年のワシントンD.C.におけるバイクレンタル数のデータをニューラルネットワークに学習させて、予測するモデルを
作成するという課題を行ったときの事を書きます。

作成したコードをGoogle Colabで公開しています。
自作ニューラルネットワーク

作成当初、入力数値を標準化するという考えがなく入力データをそのまま使用していたため、
シグモイド関数がサチュレートしてしまい、うまく計算できないという現象に出くわして、右往左往していました。

結局いろいろ調べて、入力に用いる値は標準化する事や、One-Hot Encodingするべき値などについて理解して、ニューラルネットワークの自作に成功しました。

今回作成したものは、CNNなどの畳み込み処理を行っていないことや、入力層と隠れ層、出力層の3層のみでディープニューラルネットワークではない事から、
LeRU関数ではなく、古典的なシグモイド関数を使用しました。

以下のスクリーンショットが、教師データ以外のデータから、その日に貸し出されるだろうと予測されたレンタルバイクの台数と、
実際の貸し出されたレンタルバイクの台数を比較したものです。
f:id:tyusuke88:20191112152132p:plain

適合はまずまずのようです。

最後に、学習を進めていくうちに、訓練データでの精度は上がり続けるにもかかわらず、訓練データに使用していない検証用データでの精度が
落ち始めて、過学習が進んでいく過程を含めた全体のエラー率(学習モデルの誤差)をグラフにしたものです。

f:id:tyusuke88:20191112153222p:plain
訓練の過程

Djangoで作ったライセンス認証サーバーの負荷試験をしてみた。

先日DjangoのRestFrameworkを活用して作成した、ライセンス認証サーバーがどれくらいの負荷に耐えられるのか自分の中で自信がなかったため、
負荷試験を行う方法を探していたのですが、どれも設定が難解だったり、GETリクエストの負荷試験の方法は分かっても、
POSTリクエストを使用した負荷試験の方法は分からなかったりあまり使い勝手がいいものが見つからない中、これであればというものが見つかりました。
app.loadimpact.com

あらかじめ想定されるアクセスの内容を記述しておき、必要であれば待機時間を設けて複数の内容で負荷試験を行うものです。
今回は、前回作成したEAのライセンス認証サーバーを想定して、複数のアカウントを異なるVU(仮想ユーザーと呼ぶようです)からアクセスした状態を再現して、
返って来るレスポンスの内容が、予め指定されたものと同一かどうかで成功・失敗を判定します。
以下のスクリーンショットのテスト内容では、5VUs(5人の仮想ユーザー)で60秒間テストし、10VUsに増やして60秒間、20VUsに増やして60秒間、30VUsに増やして60秒間、
f:id:tyusuke88:20191109134014p:plain
後は、同じように、30VUs→20Vus→10VUsと60秒ごとに負荷を減らしていき、結果を見るというものです。

ライセンス認証サーバーにはAWSのt2.microインスタンスを使用しているため、そもそも30VUsには同時には耐えられないと思いますが、一応実行してみます。
f:id:tyusuke88:20191109135649p:plain
結果のグラフを見ると、設定ファイルで設定したよりは細かいステップでVUs(仮想ユーザー)を増やしているようです。
そして、仮想ユーザーがピークの30VUsに達した後に、サーバーからのレスポンスがステータス500を返すようになり、最後までそのままで終了しました。

仮想ユーザーが減少しても、サーバーエラーが戻らないのには、ロジック上の改善点がまだあるのかも知れませんが、t2.microでは、
同時に30人のユーザからアクセスがあった場合には、応答しきれずサーバーがダウンしてしまう様子が確認出来ました。

MT4のEA向け簡易ライセンス認証サーバーをDjangoで作ってみた

django rest frameworkを使用して、EAのライセンス認証サーバーを作ってみました。
構造はシンプルで、予め管理者画面で使用を許可するEAのアカウント番号を登録しておき、
エンドポイントURLにペイロードとしてJSON形式でアカウント番号を送信して、
データベースの一覧にあればステータス200を返す、無い場合はそれ以外を返すというものです。

実際にはEAのアカウント番号を用いてPOSTリクエストした際に応答としてトークンを返信しているのですが、
このトークンは実装しただけで未使用です。
トークンを利用すると、ライセンス認証サーバーから帰ってきたトークンに制限時間を設けて、時間切れになったら再度トークンを発行するといった感じで、
時間的に細かい使い方が出来るようになります。

EA→ライセンス認証サーバー→データベース

データベースの一覧にアカウント番号があった場合は、
ライセンス認証サーバー→トークン(EAへ)

データベースの一覧にアカウント番号が無かった場合は、
ライセンス認証サーバー→403エラー

といった具合に、応答を変えて自分自身が接続しているアカウント番号がライセンス認証サーバーに登録されているかどうか確認するというものです。

ただ、内容が暗号化されていない事とトークン自体が適切なものかを判別するロジックがないため、簡易版といった状態です。

今後、正規のライセンス認証サーバーから帰った来たトークンかどうかを判別できるようにしてもう少しセキュリティーを向上させようと思います。