Arduino IDE で構造体宣言があるにも関わらず 'not declared in this scope' エラーが発生する

概要

タイトルのとおりですが、Arduino IDEC/C++ の文法に則って記述した構造体宣言があるにも関わらず 'not declared in this scope' でコンパイルエラーが発生する場合があります。
この原因と解決方法を書いておきます。

現象

sketch.ino こコードの一部を記載しますが、構造体が関数の前に宣言されており、C/C++ の文法に則っています。

typedef struct _T_NUM{
  uint8_t  status;
  uint16_t peak;
} T_NUM;

void get_num(T_NUM* _que)
{
  ....
  return;
}

しかしながら、Arduino IDEコンパイルをすると次のコンパイルエラーとなります。

sketch:159:22: error: variable or field 'get_num' declared void
 void get_num(T_NUM* _que)
                      ^~~~~
sketch:159:22: error: 'T_NUM' was not declared in this scope
sketch:159:29: error: '_que' was not declared in this scope

原因

Arduino IDEコンパイル時にスケッチを元に独自の方法で関数プロトタイプ宣言などを挿入して C ソースコードを生成します。
ここで、構造体宣言の関数プロトタイプ宣言の順が前後してしまい、あたかも宣言されていないかのようになってしまうということです。

対策

いくつかあるのですが、私が有効だった方法は「宣言をヘッダーに書き、インクルードする」です。

宣言をヘッダーに書き、インクルードする。

先程の sketch を sketch.io と header.h に分割し、 header.h を sketch.io からインクルードします。

<header.h>

#ifndef HEADER_H
#define HEADER_H

typedef struct _T_NUM{
  uint8_t  status;
  uint16_t peak;
} T_NUM;

#endif

<sketch.ino>

// コード頭で
#include "header.h"

...

void get_num(T_NUM* _que)
{
  ....
  return;
}

Arduino IDEC/C++ の煩雑な部分を隠蔽して実装をしやすくしてくれるのですが、Arduino IDE のやってくれる部分とぶつかってしまう場合があるということですね。

プロトタイプ宣言をあえて前に挟む

もう一つの方法が紹介されていました。
sketch.ino に関数プロトタイプ宣言を敢えてします。

typedef struct _T_NUM{
  uint8_t  status;
  uint16_t peak;
} T_NUM;

void get_num(T_NUM* _que); // これを敢えて書くことで、IDE が宣言を自動的に挿入しないようにする。
void get_num(T_NUM* _que)
{
  ....
  return;
}

ただし、私はこの方法がうまく機能しなかったので、IDE の version などによるのかもしれません。

リファレンス

この内容は全てこの QA から抜粋しました。

forum.arduino.cc

SOC2 (Service and Organization Controls 2) 保証報告書

概要

業務で必要になったため、「SOC2 (Service and Organization Controls 2) 保証報告書」について簡単にまとめ、参考サイトも貼っておきます。

上記サイトから私の興味があった点だけピックアップしたものですので、詳細は掘り下げていただければと思います。

SOC2 とは

SOC2(Service and Organization Controls 2)は、受託業務のセキュリティ・可用性・処理のインテグリティ・機密保持に係る内部統制の監査で、サイバーセキュリティのリスクマネジメント統制に関する報告書の実質的なグローバルスタンダードです。

簡単に言うと、この報告書は受託業務のサービス提供会社の内部統制について、独立した監査法人公認会計士が第三者の立場から検証した結果が記されたものです。
したがって、外部のクラウドサービスを利用するにあたって、そのサービスの運営会社を業務委託先として評価する際に、内部統制の視点からの有用な報告書だと言えます。

ISMS との違い

日本では ISMS情報セキュリティマネジメントシステム)の認証を取得している企業も多いかと思います。 定期的に、ISMS 監査があるのでデスク周りに機密書類を置かないようにしてください、などのアナウンスが回ってくることもあるでしょう。

まずSOC2は、外部から『保証報告書として提供』されるものであり、ISMSのような何らかの「認証を取得する」ものではありません。ここがピンとこない方もいるのでは。

ISMSは、情報セキュリティに対するマネジメントシステムの仕組みが、そのクラウドサービスを運営している企業にある一定以上の水準で『存在する』ことを証明しているだけで、そのサービスそのものがどういう内部統制で運営されているかを知ることができません。また、監査で部分的に問題が見つかっても改善されていれば証明書が発行されます。一体どのような不適合が見つかったのか、何がどう改善されたのかを知る手段がありません。

SOC2保証報告書は、内部統制の仕組みが詳細に記述されているため、入手して読めば、そのクラウドサービスのセキュリティ設計や運用に関する具体的な手続きについて知ることができます。また、監査で検出された内容がありのまま記載されています。原則として、報告書はそのまま提出するルール(抜粋等はNG)なので、そのサービスを利用して問題ないかどうかの判断材料として有効だと言えるでしょう。

SOC2 保証報告書は ISMS のように認証ではないため、実際のドキュメントは数十ページにわたる監査項目と実際の内部統制の仕組みの詳細が細かく記されています。
ですのでクラウドのシステム構成、ネットワークアーキテクチャや HR のロール(オンボーディング、トレーニングやセパレーション)、さらに様々な IT オペレーションについての仕組みが記述されています。
そのため、ISMS のように「内部統制の仕組みがある」ことが証明されているだけの認証よりも、もう一段深く踏み込んだ報告となっています。

まとめ

これから SOC2 保証報告書を取得しようとしている方だけでなく、SOC2 を取得している企業のサービスを利用している方も いるでしょう。
よくセキュリティ部門から聞かれるのが「ISMS と何が違うの? ISMS 認証取得していないけど大丈夫(SOC2で大丈夫)?」といったことかと思いますが、基本的に ISMS と同等に(か、それ以上に)そのサービスの運営会社を業務委託先として評価する際に有用なものです。

ほとんどこちらの記事から抜粋させていただきました。
cloudpack.media

anaconda + screen で activate 時にエラー発生

始めに

私は screen 環境をよく使うのですが、新しい環境で anaconda を初めて使うときなどによく発生するエラーとして以下があります。

何度もハマっている(のに忘れる)ため、備忘のため書いておこうと思います。

エラーについて

どういう時に発生するかと言うと、

1. anaconda を install します。

参考:https://entre-temps.hatenablog.com/entry/setup_machinelearning_env

2. 新しい env を作ります。

$ conda create -n py36_base python=3.6
$ conda info -e
# conda environments:
#
base                  *  /anaconda3
py36_base                /anaconda3/envs/py36_base

3. screen で使おうと、起動し conda activate py36_base するとエラーが発生します。

~ $ conda activate py36_base

CommandNotFoundError: Your shell has not been properly configured to use 'conda activate'.
To initialize your shell, run

    $ conda init <SHELL_NAME>

Currently supported shells are:
  - bash
  - fish
  - tcsh
  - xonsh
  - zsh
  - powershell

See 'conda init --help' for more information and options.

IMPORTANT: You may need to close and restart your shell after running 'conda init'.

原因

何が原因でこれが起こるかというと、要するに .bash_profile を screen はデフォルトではロードしないのです。

.bash_profile には下記の anaconda の初期設定スニペットがインストール時に自動的に追記されます。

そのため、ここがロードされないと正常に環境変数などが設定されないことになり、先程のエラーに繋がります。

# added by Anaconda3 2019.10 installer
# >>> conda init >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$(CONDA_REPORT_ERRORS=false '/opt/anaconda3/bin/conda' shell.bash hook 2> /dev/null)"
if [ $? -eq 0 ]; then
    \eval "$__conda_setup"
else
    if [ -f "/opt/anaconda3/etc/profile.d/conda.sh" ]; then
# . "/opt/anaconda3/etc/profile.d/conda.sh"  # commented out by conda initialize
        CONDA_CHANGEPS1=false conda activate base
    else
        \export PATH="/opt/anaconda3/bin:$PATH"
    fi
fi
unset __conda_setup
# <<< conda init <<<

# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$('/opt/anaconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
    eval "$__conda_setup"
else
    if [ -f "/opt/anaconda3/etc/profile.d/conda.sh" ]; then
        . "/opt/anaconda3/etc/profile.d/conda.sh"
    else
        export PATH="/opt/anaconda3/bin:$PATH"
    fi
fi
unset __conda_setup
# <<< conda initialize <<<

解決方法

至極単純で、.screenrc に次の一行を加え、screen 起動時に .bash_profile を読むように設定すれば解決します。 https://serverfault.com/questions/226783/how-to-tell-gnu-screen-to-run-bash-profile-in-each-new-window/226788

shell -$SHELL

以上です。

ラズパイでパスワードを忘れた際のリセット方法

この記事の内容

しばらく箪笥にしまっていたラズパイを久々に引っ張り出してきてログインしようとしたらパスワードを忘れて、ということがよく起こります。
ここでは、パスワードをリセットする方法について書きます。

ラズパイでパスワードを忘れた際の方法

必要なもの

  • ディスプレイ
  • キーボード

環境

Raspberry Pi 2 Model B V1.1

$ cat /etc/debian_version
9.3

手順

ほぼこちらを参考に、少しコマンドエラーを修正しました。 https://canalier.com/raspberry-pi%E3%81%AE%E3%83%91%E3%82%B9%E3%83%AF%E3%83%BC%E3%83%89%E5%BF%98%E3%82%8C%E3%81%9F%E6%99%82%E3%81%AE%E5%AF%BE%E5%87%A6/

# /Volumes/boot/cmdline.txt
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait init=/bin/sh
# su
root@(none):/# mount -rw -o remount /dev/mmcblkop2 /
...... EXT4-fs (mmcblk0p2): re-mounted. Opts: (null)

root@(none):/# pwconv
root@(none):/# passwd pi

Tree における単調性制約

XGBoost における単調性制約 - 2

はじめに

前回 XGBoost での単調性制約を与える方法について紹介しましたが、そのメカニズムについて調べました。



線形モデルはデータと目的変数間の単調性(増加・減少)を捉えるモデルです。 非線形モデルを使うのは、単調性にとどまらない表現力を利用したい場合があるからです。

実データを用いていると、特定の説明変数にはすでに単調性があることが分かっていたりします。そして非線形モデルを使う場合、特定の説明変数に単調増加の制約を与えるなど、局所的にコントロールしたいときがあります。

例えば、決定木・XGBoost などでモデリングをしていると、バイアスの低さにとても助けられますが、同時に強力すぎる分割の力をなんとか制御したいと思うこともあります。
そのような、モデルの複雑さをコントロールするためにも単調性制約は使えます。
また、決定木などツリー系はロジックそのものが木で表現できるので、複雑度をうまくコントロールすると説明性が上がります。

Tree における単調性制約の考え方と実装

単調性を持った問題への分類ツリーの活用については以下の論文が理論的にあります。
http://helios.mm.di.uoa.gr/~rouvas/ssi/sigkdd/sigkdd.vol4.1/potharst.pdf

実装の考え方については、次の記事に非常に分かりやすくかつ詳しく書いてあったので、ほぼそのまま引用させてもらいました。
towardsdatascience.com

以下は、上記の記事の意訳になります。

この記事では、単調性制約の与え方を 2 段階に分けて説明しています。

1. 左右子ノードの重みで分割の是非を判断する

Tree における単調性制約の基本的な考え方は、

枝を生やしていく過程で、分割した際に単調性ではない関係が作られた場合は、その分割を取りやめる。

ことです。

例えば、特徴量 f に単調増加制約を与えたいとして、あるノードの分割の際に f がピックアップされ分割される場合を考えてみます。
この時、制約が単調増加なので、右子ノードの重み w_R は左子ノードの重み w_L よりも大きいことが期待されます。
アルゴリズムでは

w_L <= w_R の場合のみ分割し、そうでなければ分割をしない

となります。

この処理は XGBoost の以下のコード L.441,442 に相当します*1

github.com




この単調性制約の実装はとてもわかりやすい方法です。
しかしながら、単純にこの方法を実装しただけではうまく機能しないことにすぐ気づくと思います。
なぜならば、そのノード・その特徴量にのみ注目する限りに於いてはうまくいっていますが、木全体を見た場合単調性制約が守られない場合があるからです。(局所的には守られているが、大域的には破綻している)

もし、下流のノードで同じ特徴量が再度分割対象として取り上げられたらどうなるでしょう?
そのノードでも単調増加制約が守られ、左左子ノードと左右子ノードが単調増加性を持って重み w_LL <= w_LR となるように分割されたとしても、w_LR が右子ノードの重み w_R よりも高くなる場合がありえます。
このように、単調増加関係を木全体に渡って維持するためには、右子ノードの重み w_R は左子ノードの重み w_L よりも高いだけでなく、その子孫ノード { LL, LR, LLL, LLR, LRL LRR....} の重みよりも常に高くないとなりません。

それでは、どのようにして、この様なケースに対処するのでしょうか。 言い方を変えると、どのように制約を木の深さを超えて与えることができるのか、ということです。



2. 単調性制約を木全体に与える

XGBoost では次のように対処しています。

ノードを分割する時、子ノード(LL, LR)の重み(w_LL または w_LR)は親ノード(L)と親ノードの兄弟ノード(R)の重みの平均を境界とします*2
この制約を与えると、特徴 f が木の分割の中で何度も分割されたとしても、重みが祖先ノードよりも高く(或いは低く)なり、木全体に渡って単調性制約に違反しないことが保証されます。

上記のこの書き方だと少し分かりづらいので、具体的な例で考えてみます。

あるノードを単調増加性をもって左子ノード(L)と右子ノード(R)に分割し、更に左子ノードを分割(LL, LR)する場合、 分割の際に w_LL <= w_LR の条件に加えて、w_LR <= mean(w_L, w_R) の条件を課します*3

この2つの条件を課すことで、特徴 f の分割に関しての単調性制約は木全体でも保持されることとなります。

この条件の実装はXGBoost の以下のコード L.464-467 に相当します。
github.com




フロー

上記の2つの条件を、フローにしてみます。
特徴 x1 に単調増加制約を与えるとします。

  1. ルートノードでは、w_0 のみが生成されます。

  2. ルートノードの分割に於いて、特徴 x1 がピックアップされた場合、w_L <= w_R の条件に適合する分割のみ採用され、その中でゲインが最良の分割値で分割されます。*4

  3. 左子ノード(L)の分割をします。 特徴 x1 がピックアップされた際、w_LL < w_LR になる分割のみ採用されます。

  4. ルートノード以降の分割では 3 に加えて次の条件を課します。 w_LL < mean(w_L, w_R), w_LR < mean(w_L, w_R)

  5. 同様に、右子ノード(R)を分割する際にも 3,4 の制約を与えます。
    但し、4 に相当する条件は w_RL => mean(w_L, w_R), w_RR => mean(w_L, w_R) となります。

  6. 木を生成する過程で 3,4,5 の手順が守られると、x1 に対して左の部分木の重みは右の部分木の重みよりも常に小さくなります。したがって、単調増加性制約は木の全体に対して課せられます。

このフローに従った分割は下図となります。




まとめ

この記事の内容紹介をしながら、XGBoost における単調性制約の考え方と実装を見てみました。

次回は、木の数がひとつで、XGBoost よりもシンプルな決定木アルゴリズムにこの条件を実装し、動きを見てみたいと思います。

*1:条件が合わなければゲインは負の無限大に置き換えられ、その結果アルゴリズムは分割をしない、という方法で実装されています。単調減少制約の場合は逆です

*2:単調増加制約の場合は上限、単調減少制約の場合は下限

*3:mean は平均関数

*4:もちろん、他の特徴でよりよりゲインがあれば、それが採用されます

matplotlib で plt.show() するとウィンドウは開いてもグラフが描画されない

matplotlib で plt.show() するとウィンドウは開いてもグラフが描画されない、という問題が発生しました。
plt.show() で ウィンドウが開かない、というはよくあるパターンで大体が描画の際のバックエンドの指定だと思います (https://qiita.com/yoshizaki_kkgk/items/bcb45e3bc936ec49ef00)。 今回は、それとは異なった現象・原因であったため残しておきます。

結論

どうやら tk のバージョンに起因するようです。

tk 8.6.8 and tk 8.6.9 build 0 and build 1000 are fine.
tk 8.6.9 build 1001 and build 1002 are not.
macOS 10.14.5 (18F132)

github.com

github.com

対処

私は anaconda を使っており、tk のダウングレードなどで他のパッケージへの影響がわからなかったため、新たに conda 環境を作り直しました。
こういう場合は anaconda は本当に楽だなと思います。

$ conda create -n py36_base python=3.6
$ conda info -e
# conda environments:
#
base                  *  /anaconda3
py36_base                /anaconda3/envs/py36_base

$ source activate py36_base

$ conda install ipython ipdb
$ conda install pandas matplotlib seaborn
$ conda install scikit-learn

$ conda list | grep tk
tk                        8.6.8                ha441bb4_0

# 動作確認
# 以下のサンプルを実行できることを確認します。
# sklearn + matplotlib https://scikit-learn.org/stable/auto_examples/plot_multilabel.html#sphx-glr-auto-examples-plot-multilabel-py

デフォルトでは tk=8.6.8 であったことから、どうやら他のパッケージをアップグレードした拍子に tk も 8.6.9 に上げてしまったようでした。

CalibratedClassifierCV で XGBoost の学習済みモデルが ValueError: feature_names mismatch となる

はじめに

CalibratedClassifierCV に XGBoost の学習済みモデルを指定して、fit すると ValueError: feature_names mismatch となる場合があります。
これは XGBoost の学習時には pandas の column 名を変数とし、CalibratedClassifierCV では numpy.ndarray に変換されるため、説明変数名の不一致が起きてしまうことに起因しています。
XGBoost の学習済みモデルを用いて補正する場合に限定されますが、それを回避する方法を記載します。

Probability Calibration とは

Probability Calibration(確率補正)は、モデルによって予測された確率を本来の確率に補正して近づける手法です。
詳細は別途機会があれば整理したいと思いますが、とても簡単に言うと機械学習のモデルの推定する値は確率とは異なる場合があります(確率を推定するモデル以外は)。

「分類器としての性能と,確率を正しく推定できるかは分けて考えないといけないのだろうな.」https://twitter.com/nakamichi/status/797361213185335296

その推定値を確率として補正する、ということだろうと思います。

tjo.hatenablog.com

CalibratedClassifierCV で XGBoost 学習済みモデルを補正

上述のように、CalibratedClassifierCV に XGBoost の学習済みモデルを指定して、fit すると ValueError: feature_names mismatch となる場合があります。

ValueError: feature_names mismatch: ['a', 'b', ...] ['f0', 'f1', ']
expected f0 f1 .
training data did not have the following fields: a b ...

これは簡単に言うと、学習時のデータが pandas.DataFrame であった場合、CalibratedClassifierCV では numpy.ndarray に変換されるため、説明変数名の不一致が起きてしまうことに起因しています。

sklearn の XGBClassifier は pandas.DataFrame を学習データとして投入された時、feature names を column 名として保持します。
それに対して、CalibratedClassifierCV は fit 関数内部で pandas.DataFrame を numpy.ndarray に変換して学習済みの XGBClassifier Object にわたすため、feature names の不一致が起きてしまい ValueError となってしまうようです。
一方、 CalibratedClassifierCV で学習も同時に実行する場合は、XGBClassifier では numpy.ndarray が内部的に DMatrix 形式に変換され、feature names は ['f0', 'f1', '] のように設定されるため、このような不一致は起きなくなります。

これについての報告や詳細な考察は以下にあります。

github.com

calibration.py の class CalibratedClassifierCV.fit をたどると、さまざまな validation を通り抜けて、最終的に cv='prefit' (既に学習済みのモデル)である場合、_CaliratedClassifier.fit で確率補正学習します。
その vaidation の中で、dataframe のX,y は numpy.array に変換されます。
具体的には validation.py の indexable という関数にて、データ数のチェックや sparse 行列の csr 変換や non-iterable object を配列に変換する処理をしている箇所です。

def indexable(*iterables):
    """Make arrays indexable for cross-validation.

    Checks consistent length, passes through None, and ensures that everything
    can be indexed by converting sparse matrices to csr and converting
    non-interable objects to arrays.

こちらから validation 済みのデータを渡すのであれば、特にここを通す必要はなく、更に学習済みモデルを補正する場合は直接 _CalibratedClassifier で fit すればよいです(L.155のあたり)。
(一方、CrossValidation を行う場合は L.162 の交差検証実行されます。)

# L.155
calibrated_classifier = _CalibratedClassifier(base_estimator, method=self.method)
    
if sample_weight is not None:
    calibrated_classifier.fit(X, y, sample_weight)
else:
    calibrated_classifier.fit(X, y)

実行結果として適切なものがなくてコードだけになってしまいますが、動作を確認したものを簡単に記載します。

import xgboost as xgb
from _calibration import _CalibratedClassifier

#
# model は xgb.XGBClassifier の学習済みモデル
# X_test, y_test は model のテストデータ
#

sig_clf = _CalibratedClassifier(model, method='sigmoid')
sig_clf.fit(X_test, y_test)

# 補正後の値
y_prob_sig = sig_clf.predict_proba(X_test)[:, 0]

# 補正前の価
y_prob = model.predict_proba(X_test)[:, 0]

# plot ...