機械学習の前処理、EDA、モデル構築手順(回帰問題)

自分用のメモです。 随時更新します。

可視化はmatplotlib系(seaborn)で行う。 ほかに候補としてはplotlyなどあるので好きなのを選べばよい。

回帰問題

importするライブラリ

# データ解析のライブラリ
import pandas as pd
import numpy as np 
import pandas_profiling as pdp

# データ可視化のライブラリ
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns
%matplotlib inline 

# Scikit-learn
# common
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV 
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import MinMaxScaler
from sklearn.cross_validation import cross_val_score
from sklearn.pipeline import Pipeline

# 評価指標
from sklearn.metrics import mean_squared_error, r2_score

# 推定器(とヘルパー関数)
from sklearn.linear_model import LinearRegression # 線形回帰
from sklearn.linear_model import SGDRegressor # 確率的勾配降下法
from sklearn.linear_model import Ridge, Lasso, ElasticNet # 正則化された線形回帰
from sklearn.tree import DecisionTreeRegressor # 決定木
#
from sklearn.preprocessing import PolynomialFeatures # 多項式回帰

# scipy
from scipy import stats

# XGBoost
import xgboost as xgb
from xgboost import XGBRegressor

# その他
from math import sqrt

データの読み込み

df = pd.read_csv("hoge.csv")

EDA

読み込んだデータの素性をみる

print(df.shape, df.dtypes)
df.head()
df.describe()
pdp.ProfileReport(df)

量的データ

特徴量(目的変数を含む)同士の相関

目的変数と相関が低い特徴量は捨てる、相関が高い特徴量同士は特徴が似ているので減らす、etc.

# 散布図,ピアソン相関係数
g = sns.jointplot(<some_feature>, <target>, data = concrete, kind='scatter', color='darkslategray').annotate(stats.pearsonr)
# 回帰,ピアソン相関係数
g = sns.jointplot(<some_feature>, <target>, data = concrete, kind='reg', color='darkslategray').annotate(stats.pearsonr)
# corr heatmap 
df_corr=df[["peak-rpm", "highway-mpg", "price"]].corr()
g=sns.heatmap(df_corr, square=True, vmax=1, vmin=-1, center=0, annot=True)

質的データ

ペアプロット

特徴量同士の散布図とヒストグラム

sns.pairplot(glass[['RI', 'Na', 'Mg', 'Al', 'Type']], hue='Type', diag_kind='hist')

箱ひげ図(box plot)、Swarmplot(分布密度を表現)

分布がオーバーラップしていなければ使えそう

features = glass.iloc[:,0:4].columns

plt.figure(figsize=(20,9*5))
gs = gridspec.GridSpec(4, 1)
for i, col in enumerate(glass[features]):
    plt.title('Glass features')
    ax = plt.subplot(gs[i])
    sns.boxplot(x= glass['Type'], y=glass[col], palette='Set2', linewidth=1.0)
    sns.swarmplot(x=glass['Type'], y=glass[col], color=".5")

前処理

NaNでない不定値をNaNへ置換

# 例:不定値が"?"で表現されている場合
# inplace=Trueのときは、dfを書き換える
df.replace("?", np.nan, inplace = True)

NaNの処理(置換(平均、最頻値、補間、)、その列/行を捨てる)

# replace by mean value. 
avg_norm_loss = df["normalized-losses"].astype("float").mean(axis=0)
print("Average of normalized-losses:", avg_norm_loss)
df["normalized-losses"].replace(np.nan, avg_norm_loss, inplace=True)
# replace by median value.
df['num-of-doors'].value_counts().idxmax()
df["num-of-doors"].replace(np.nan, "four", inplace=True)

正規化

# 値が正の場合、最大値で割って値域を0~1にする
df['length'] = df['length']/df['length'].max()
# https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
df = scaler.transform(df)

標準化

# 平均を引いて標準偏差で割る。平均ゼロ、分散1の集合にする。
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
scaler.fit(df)

分布を正規分布に近づける(Box-Cox変換、Yeo-Johnson変換)

正規分布を仮定してるモデル手法が多いので

# https://note.com/mikiokubo/n/n42417e5d0f6c
from scipy import stats
data, lmbda = stats.boxcox(df["pm2.5"]+0.001)
df["boxcox"] = data
df["boxcox"].hist()

次元削減

PCAとか

from sklearn.decomposition import PCA

pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_Train)

Binning(あんまり実施しなさそう)

大(=2)、中(=1)、小(=0)などに分類する

bins = np.linspace(min(df["horsepower"]), max(df["horsepower"]), 4)
group_names = ['Low', 'Medium', 'High']
df['horsepower-binned'] = pd.cut(df['horsepower'], bins, labels=group_names, include_lowest=True )

pyplot.bar(group_names, df["horsepower-binned"].value_counts())

# set x/y labels and plot title
plt.pyplot.xlabel("horsepower")
plt.pyplot.ylabel("count")
plt.pyplot.title("horsepower bins")

カテゴリー特徴量の変換

# onehot encoding(dummy)
# e.g.: fuel-type is "gas" or "diesel"
dummy_variable_1 = pd.get_dummies(df["fuel-type"])

# merge data frame "df" and "dummy_variable_1" 
df = pd.concat([df, dummy_variable_1], axis=1)

# drop original column "fuel-type" from "df"
df.drop("fuel-type", axis = 1, inplace=True)

ベースmodel作成

比較対象とする最初のモデル。とりあえずXGBoostで。

xgboost = XGBRegressor(random_state=1)
history = xgboost_base.fit(X_train, y_train)
y_pred_base = xgboos.predict(X_train)
# e.g. 評価指標がRMSE(Root Mean Squard Error)のとき
rmse_base = sqrt(mean_squared_error(y_train.values, y_pred_base))
print(Rmse_base)

モデル性能評価

クロスバリデーション

from sklearn.model_selection import StratifiedKFold

# kfoldイテレータ
kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=1)
for train_idx, val_idx in kfold.split(train_features, train_labels):
    history = model.fit(
        train_features[train_idx], 
        train_labels[train_idx],
        epochs=30,
        batch_size=20,
        validation_data=(train_features[val_idx], train_labels[val_idx])
    )
from sklearn.cross_validation import cross_val_score

scores = cross_val_score(
    estimator=pipe_lr,
    X=X_train,
    y=y_train,
    cv=10,
)

ハイパーパラメータチューニング

グリッドサーチ

from sklearn.grid_search import GridSearchCV

# グリッドサーチCVの実行
test_params = {
 'n_estimators':[100,200,300,400,500]
}
gs = GridSearchCV(
    estimator = XGBRegressor(seed=42), 
    scoring='explained_variance', 
    param_grid = test_params, 
    cv = 5, 
    early_stopping_rounds=50, # 50回改善しないときに終了
    return_train_score=False
)
gs.fit(X_train,y_train)

# 最良スコア、パラメータ
print(gs.best_score_)
print(gs.best_params_)

# 最良モデルパラメータで推測
clf = gs.best_estimator_
clf.fit(X_train, y_train)

pipeline

ワークフローの効率化。fit_transformメソッドを持つ変換器や推定器を結合する。

from sklearn.pipeline import Pipeline

pipe_lr = Pipeline([
    ("scl", StandardScaler()),
    ("pca", PCA(n_components=2)),
    ("clf", LogisticRegression())
])
pipe_lr.fit(X_train, y_train)

学習曲線を描く

# https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.learning_curve.html#sklearn.model_selection.learning_curve
# https://scikit-learn.org/stable/auto_examples/model_selection/plot_learning_curve.html#sphx-glr-auto-examples-model-selection-plot-learning-curve-py
# https://qiita.com/koichi_hiphopdream/items/9ad75d184aba9626c09b

def plot_learning_curve(estimator, title, X, y, axes=None, ylim=None, cv=None,
                        n_jobs=None, train_sizes=np.linspace(.1, 1.0, 5)):

    if axes is None:
        _, axes = plt.subplots(1, 3, figsize=(20, 5))

    axes[0].set_title(title)
    if ylim is not None:
        axes[0].set_ylim(*ylim)
    axes[0].set_xlabel("Training examples")
    axes[0].set_ylabel("Score")

    train_sizes, train_scores, test_scores, fit_times, _ = \
        learning_curve(estimator, X, y, cv=cv, n_jobs=n_jobs,
                       train_sizes=train_sizes,
                       return_times=True)
    train_scores_mean = np.mean(train_scores, axis=1)
    train_scores_std = np.std(train_scores, axis=1)
    test_scores_mean = np.mean(test_scores, axis=1)
    test_scores_std = np.std(test_scores, axis=1)
    fit_times_mean = np.mean(fit_times, axis=1)
    fit_times_std = np.std(fit_times, axis=1)

    # Plot learning curve
    axes[0].grid()
    axes[0].fill_between(train_sizes, train_scores_mean - train_scores_std,
                         train_scores_mean + train_scores_std, alpha=0.1,
                         color="r")
    axes[0].fill_between(train_sizes, test_scores_mean - test_scores_std,
                         test_scores_mean + test_scores_std, alpha=0.1,
                         color="g")
    axes[0].plot(train_sizes, train_scores_mean, 'o-', color="r",
                 label="Training score")
    axes[0].plot(train_sizes, test_scores_mean, 'o-', color="g",
                 label="Cross-validation score")
    axes[0].legend(loc="best")

    """
    # Plot n_samples vs fit_times
    axes[1].grid()
    axes[1].plot(train_sizes, fit_times_mean, 'o-')
    axes[1].fill_between(train_sizes, fit_times_mean - fit_times_std,
                         fit_times_mean + fit_times_std, alpha=0.1)
    axes[1].set_xlabel("Training examples")
    axes[1].set_ylabel("fit_times")
    axes[1].set_title("Scalability of the model")

    # Plot fit_time vs score
    axes[2].grid()
    axes[2].plot(fit_times_mean, test_scores_mean, 'o-')
    axes[2].fill_between(fit_times_mean, test_scores_mean - test_scores_std,
                         test_scores_mean + test_scores_std, alpha=0.1)
    axes[2].set_xlabel("fit_times")
    axes[2].set_ylabel("Score")
    axes[2].set_title("Performance of the model")
    """

    return plt

# cv対応無し
def plot_simple_learning_curve(model, X, y):
    X_train, X_val, y_train, y_val = train_test_split(X, Y, test_size=0.2)
    train_errors, val_errors = [], []
    for m in range(1, len(X_train)):
        model.fit(X_train[:m], y_train[:m])
        y_train_predict = model.predict(X_train[:m])
        y_val_predict = model.predict(X_val[:m])
        train_errors.append(mean_squared_error(y_train_predict, y_train[:m])
        val_errors.append(mean_squared_error(y_val_predict, y_val[:m])
    plt.plot(np.sqrt(train_errors), "r-", label="train")
    plt.plot(np.sqrt(val.errors), "b-", label="val")

評価指標

model.fit(X_train, y_train)
y_pred = model.predict(X_train)

# MSE
mean_squared_error(y_train, y_pred)
# R2 score, 最大1で誤差がないことを示す。
r2_score(y_train, y_pred)

機械学習の設計パターン(マクロ&ミクロ)

自分用のメモです。

マクロなパターン(≒システム、運用)

tech.mercari.com

ミクロなパターン(≒前処理、EDA、モデル構築)

courses.edx.org

www.kaggle.com

  • 前処理
    • cell_17: 経験則ぽいEDA
    • cell_25: columnsの選択基準がよくわからない
    • cell_27: サンプルの半分以上が目的変数に対して強い相関を持つ特徴量の抽出。17/81
    • cell_30: outlier(外れ値)排除
    • cell_: 目的変数SalesPriceを対数に変換
  • モデル作成
    • cell_40: 一般的な正規化StandardScaler()の他に RobustScaler(), PowerTransformer() ?? も別途実施。結局使っているのは、PowerTransformer()だけ。変数のスケールを変えて分布を正規分布(ガウス分布)の形に変えてくれる変換らしい。
    • cell_42~46: 推定器としてregressor()を5種類生成。選択基準は不明。
    • ell_47: () で 5つのregressorをスタッキング。

scikit-learn.org

www.kaggle.com

www.kaggle.com

機械学習で参考になった情報

機械学習を勉強するにあたって参照した情報源の自分用メモです。 (手を付けたが完了できなかった項目への自戒もこめて)

履修済みで役にたっている情報

深層学習 (機械学習プロフェッショナルシリーズ)

深層学習 (機械学習プロフェッショナルシリーズ)

  • 作者:岡谷 貴之
  • 発売日: 2015/04/08
  • メディア: 単行本(ソフトカバー)
深層学習という言葉が流行りだしたときに一番最初に読んだ本。というか日本語で自分が読めそうな本はこれしかなかった。

deep learning だけでなく、sklearnを使った shallow learning の習得に役立つ

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装

  • 作者:斎藤 康毅
  • 発売日: 2016/09/24
  • メディア: 単行本(ソフトカバー)
3冊出ているうちの1冊目。理解できるか不安になりながら、おっかなびっくり読んだが丁寧に書いてあってよい本。

PythonとKerasによるディープラーニング

PythonとKerasによるディープラーニング

  • 作者:Francois Chollet
  • 発売日: 2018/05/28
  • メディア: 単行本(ソフトカバー)
tensorflowを直接使うのは辛そうだったので、kerasの使い方を習得するために購入。筆者はkerasの作者なので安心。

イラストで学ぶ ディープラーニング 改訂第2版 (KS情報科学専門書)

イラストで学ぶ ディープラーニング 改訂第2版 (KS情報科学専門書)

  • 作者:山下 隆義
  • 発売日: 2018/11/19
  • メディア: 単行本(ソフトカバー)
イラストがたくさん載ってるけど結構情報量は多くて参考になる

一つ一つの項目の情報量は少なめだが、その分読みやすく、他の本のインデックスとしても使える。

苦手意識のあるpandasの使い方の参考になった

scikit-learnとTensorFlowによる実践機械学習

scikit-learnとTensorFlowによる実践機械学習

  • 作者:Aurélien Géron
  • 発売日: 2018/04/26
  • メディア: 単行本(ソフトカバー)
実践向き

仕事ではじめる機械学習

仕事ではじめる機械学習

実践向き

Kaggleで勝つデータ分析の技術

Kaggleで勝つデータ分析の技術

Kaggleに挑戦しようとして読んだ。タイタニック以外もサブミットできるところまでは進めた。

実践チュートリアル 決定木とランダムフォレスト student.codexa.net 決定木をなんとなくしか理解していなかったので。

実践チュートリアル XGBoost student.codexa.net KaggleでXGBoostがよく使われるということなので勉強してみた。

courses.edx.org IBMの講座。内容は難しくないが機械学習全体のフローを再確認するのに役立った。

着手したが未完了で役にたちそうな本

パターン認識と機械学習 上

パターン認識と機械学習 上

長くかかりそう

これは写経して問題も解く予定

深層学習

深層学習

ドラフト版をざっくりはみた。数式でちゃんと理解したい人向け。

www.coursera.org Kaggleでの勝ち方。自分にはまだレベルが高くて挫折。Kaggleをある程度やってから受けた方がよさそう。


Lecture 1 - Welcome | Stanford CS229: Machine Learning (Autumn 2018) ご存知Stanford大のAndrew Ng先生の2018年の講座の動画。

www.udemy.com www.udemy.com www.udemy.com www.udemy.com 日本語だし、内容は分かりやすいのですが、自分が欲張っていくつも登録した結果、消化できていない。

未着手だがよく紹介されているのできっと参考になる

はじめてのパターン認識

はじめてのパターン認識

  • 作者:平井 有三
  • 発売日: 2012/07/31
  • メディア: 単行本(ソフトカバー)
はじパタ。

鈍器。

あんまり自分には合わなかったが参考になる人もいるはず

(この情報を見た時の自分の実力や求めるものと合わなかっただけで、フィットする人もいるはずです。)

直感 Deep Learning ―Python×Kerasでアイデアを形にするレシピ

直感 Deep Learning ―Python×Kerasでアイデアを形にするレシピ

Anacondaで作成した環境をJupyter Notebookで選択する

anacondaで環境作成後に下記コマンドを実行する

conda install notebook ipykernel
pip install environment_kernels jupyter
ipython kernel install --user --name <vir_env>

<vir_env> はAnacondaで作成した環境名

FreeRTOS Code Reading (Arduino編, #3)

今回は予定通り vTaskStartScheduler() から呼ばれる xTaskCreate() を読んでいく。 vTaskStartScheduler() からの の呼び出し。

  • variantHooks.cpp: void vTaskStartScheduler( void )
        /* The Idle task is being created using dynamically allocated RAM. */
        xReturn = xTaskCreate(  prvIdleTask,
                                configIDLE_TASK_NAME,
                                configMINIMAL_STACK_SIZE,
                                ( void * ) NULL,
                                portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
                                &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
  • tack.h: 関数宣言
    BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                            const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                            const configSTACK_DEPTH_TYPE usStackDepth,
                            void * const pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t * const pxCreatedTask ) PRIVILEGED_FUNCTION;
  • 関数引数
引数型 名前 内容、意味
TaskFunction_t pxTaskCode PRiVate アイドルタスク(関数)、
const char * const pcName タスク名。configIDLE_TASK_NAME, "IDLE"。
const configSTACK_DEPTH_TYPE usStackDepth タスクのスタックサイズ。configMINIMAL_STACK_SIZE, 192byte
void * const pvParameters タスクへの引数。null pointer
UBaseType_t uxPriority 0x00。portPRIVILEGE_BIT
TaskHandle_t * const pxCreatedTask NULL。xIdleTaskHandle

次はxTaskCreate() の実装。

  • tack.c
    BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                            const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                            const configSTACK_DEPTH_TYPE usStackDepth,
                            void * const pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t * const pxCreatedTask 
    {
...
        #if( portSTACK_GROWTH > 0  )
        ...
        #else /* portSTACK_GROWTH */
        {
        StackType_t *pxStack;

            /* Allocate space for the stack used by the task being created. */
            pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */

            if( pxStack != NULL )
            {
                /* Allocate space for the TCB. */
                pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */

                if( pxNewTCB != NULL )
                {
                    /* Store the stack location in the TCB. */
                    pxNewTCB->pxStack = pxStack;
                }
                else
                {
                    ...
                }
            }
            else
            {
                ...
            }
        }
        #endif /* portSTACK_GROWTH *

順にみていくが、pvPortMalloc()はヒープからメモリを確保していると思うので詳細は後で見る。

pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 

タスクのためのスタックメモリ領域を確保。サイズは StackType_t を usStackDepth個 だけ。 次にタスク制御バッファ(Tack Control Buffer。TCB_t)の領域を確保する。

pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

#defineを辿るとTCB_tの実体は下記のようである。

  • tack.c:
typedef struct TaskControlBlock_t
{
    volatile StackType_t    *pxTopOfStack;  /*< Points to the location of the last item placed on the tasks stack.  THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */

    ListItem_t          xStateListItem;     /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
    ListItem_t          xEventListItem;     /*< Used to reference a task from an event list. */
    UBaseType_t         uxPriority;         /*< The priority of the task.  0 is the lowest priority. */
    StackType_t         *pxStack;           /*< Points to the start of the stack. */
    char                pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created.  Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
}
メンバの型 メンバ変数名 内容、意味
volatile StackType_t *pxTopOfStack タスクのスタックの最後のアイテムのアドレスへのポインタ。TCB構造体のメンバの最初に置く必要がある
ListItem_t xStateListItem ステート(状態)リスト。Ready, Blocked, Suspended
ListItem_t xEventListItem イベントリストからタスクを参照するのに使われる
UBaseType_t uxPriority 優先度。0が最低。
StackType_t *pxStack スタックの先頭アドレスへのポインタ
char pcTaskName[ configMAX_TASK_NAME_LEN ] デバッグ

xStateListItem、xEventListItem の使われ方がまだよくわからない。

pxNewTCB->pxStack = pxStack;

タスク用のスタック領域(pxStack )と、タスク制御用のバッファ(pxNewTCB )が確保出来たら、タスク制御用のバッファのスタック領域に割り当てる。

後回しにしていたpvPortMalloc()を詳細にみてみる。

  • heap_3.c
void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn;

    vTaskSuspendAll();
    {
        pvReturn = malloc( xWantedSize );
        traceMALLOC( pvReturn, xWantedSize );
    }
    ( void ) xTaskResumeAll();

    #if( configUSE_MALLOC_FAILED_HOOK == 1 )
    {
        if( pvReturn == NULL )
        {
             ...
        }
    }
    #endif

    return pvReturn;
}

メモリをヒープから確保する。下記順に実行されている

  1. タスクを全部サスペンドする(vTaskSuspendAll()
  2. malloc してヒープからメモリをxWantedSize [byte] 確保する。
  3. サスペンドしたタスクをレジュームする(xTaskResumeAll()

まず、1. の全タスクのサスペンドする処理を読んでみる。

  • tasks.c:
void vTaskSuspendAll( void )
{
    /* A critical section is not required as the variable is of type
    BaseType_t.  Please read Richard Barry's reply in the following link to a
    post in the FreeRTOS support forum before reporting this as a bug! -
    http://goo.gl/wu4acr */
    ++uxSchedulerSuspended;
}

コードは1行しかないけど、コメントが何やら書いてあるので辿ってみる。 goo.gl

要約すると、最初にこの記述は問題があるのでは?という質問に対して、問題ない、と回答している。 質問者が下記質問を投げかけている。

AVR(Arduino UNOとか), ARM Cortex M ではこの操作(++uxSchedulerSuspended; のこと)をコンパイラーが以下のように処理する

  • (メモリから)uxSchedulerSuspendeded をCPUレジスタにロードする
  • レジスタ値をインクリメントする
  • レジスタ値を (メモリ上の)uxSchedulerSuspendeded にストアする

これは 明らかに NOT ATOMIC じゃない。(訳注:3命令の途中でタスク切り替えが発生し得る etc.) だから、アトミックなインクリメント機械語命令がないすべてのプラットフォームに対してportATOMIC_INCREMENT()マクロを実装することを提案する。

それに対する richardbarry さん(中の人?)の答えがこれ。

各タスクは自分自身のコンテキストを管理していて、かつコンテキストスイッチはこの値(uxSchedulerSuspended)がゼロでないときは発生しない。 従って、レジスタバックからメモリーへの書き込みはアトミックであり、問題にならない。

さらに丁寧に例を挙げて説明してくれている。

uxSchedulerSuspended がゼロから始まるシナリオを考えてみる。

  • (メモリから)uxSchedulerSuspendeded をCPUレジスタにロードする

さて、コンテキストスイッチは他のタスクが実行することを発生させる。そのタスクが同じ変数を使用する。そのタスクはその変数をゼロとしてみる。なぜなら、その変数はオリジナルタスクから更新されていないから。

ここまでは理解できる。

そのあとで、オリジナルタスクが再度実行したとする。これは以下のときのみ発生する

  • uxSchedulerSuspended が再度ゼロに設定された
  • かつ、オリジナルタスクがCPUレジスタの内容、これらはコンテキストスイッチアウトされた時のまま保持された内容であり、これを再度実行した

従って、レジスタにリードされた値はまだ(メモリ上の)xuSchedulerSuspendedの値と同じである。

うーん。この条件がよくわからない。 さらにこの後に続く質問者のコメントをみると、 uxSchedulerSuspended はカウンターではなくバイナリ(1 or 0)で扱われているから問題ないね。 と納得しているのでなんかそういうことなんだと思うけど、あとでもう一度考えてみます。

  • レジスタ値をインクリメントする

  • レジスタ値を (メモリ上の)uxSchedulerSuspendeded にストアする 他のタスクにこの変数が使用されたとしても、uxSchedulerSuspendeded は正しい値である1になる。

malloc() は stdlib.h で宣言されている。malloc実装はあとでみる。 mallocを解説した動画www.youtube.comも参考になる。

traceMALLOC() は、traceMALLOC が定義されておらずArduino UNO版では実態はないようだ。

#ifndef traceMALLOC
    #define traceMALLOC( pvAddress, uiSize )
#end

最後にxTaskResumeAll() を読もうと思ったけど、長いので次回に持ち越し。

従って、次回はvTaskStartScheduler() -> xTaskCreate() -> pvPortMalloc() から呼ばれた `xTaskResumeAll()' から続きを読んでいきます。 今回はいろいろ積み残しがありました。

FreeRTOS Code Reading (Arduino編, #2)

xTaskCreate() 関数を読む前に予定を変えてWindows環境でAruduino IDEでのビルドについて確認します。

Arduino IDEのメニューから、ファイル->環境設定->より詳細な情報を表示する->コンパイル にチェックを入れると、 下記にようにビルド時の詳細情報が表示されるようになります。

"C:\Program Files (x86)\Arduino\hardware\tools\avr/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10809 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-IC:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino" "-IC:\Program Files (x86)\Arduino\hardware\arduino\avr\variants\standard" "-IC:\Users\hogehoge\Documents\Arduino\libraries\FreeRTOS\src" "C:\Users\hogehoge\AppData\Local\Temp\arduino_build_353041\sketch\arduino_freertos_sample.ino.cpp" -o "C:\Users\hogehoge\AppData\Local\Temp\arduino_build_353041\sketch\arduino_freertos_sample.ino.cpp.o"

上の例では、下記フォルダでビルドが実行され、ファイルが生成されています

"C:\Users\hogehoge\AppData\Local\Temp\arduino_build_353041

arduino_build_folder

上記フォルダで、avr-objdump.exe を実行することで、生成されたアセンブリ言語を見ることもできます

objdump

必要に応じて利用していきたいと思います。

次回こそxTaskCreate() 関数を読み進めます。

FreeRTOS Code Reading (Arduino編, #1)

興味があるのでFreeRTOSのソースコードを読んでみる。 あとで修正するかもしれませんが、しばらくは体裁が整っていない自分用のメモです。

なぜ読むか?

RTOSの実装に興味があり、Arduino版ならコード量の少なく読みやすそうだったので。

ソースコードはここ。github.com MPU(atmega328p)のdatasheetはここ。https://avr.jp/user/DS/PDF/mega328P.pdf

もしくはここ。http://akizukidenshi.com/download/ds/microchip/atmega328.pdf

読み方、ソースコード引用の方針

どこから読み始めるか

161204_Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf に variantHook.cpp から読めと書いてあるのでそこから始める。 (↑は昔書いたメモだけど、見当たらないorz)

読む

  • Arduino_FreeRTOS_Library\src\variantHooks.cpp
extern void setup(void);
extern void loop(void);

/*-----------------------------------------------------------*/

void initVariant(void) __attribute__ ((OS_main));
void initVariant(void)
{
#if defined(USBCON)
    USBDevice.attach();
#endif

    setup();        // the normal Arduino setup() function is run here.
    vTaskStartScheduler(); // initialise and run the freeRTOS scheduler. Execution should never return here.
}

setup(), loop()はexternされていて、実体は Arduinoのsketchが参照される。

  • task.c
void vTaskStartScheduler( void )
{
BaseType_t xReturn;

    /* Add the idle task at the lowest priority. */
    #if( configSUPPORT_STATIC_ALLOCATION == 1 )
    {
        ...
    }
    #else
    {
        /* The Idle task is being created using dynamically allocated RAM. */
        xReturn = xTaskCreate(  prvIdleTask,
                                configIDLE_TASK_NAME,
                                configMINIMAL_STACK_SIZE,
                                ( void * ) NULL,
                                portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
                                &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
    }
    #endif /* configSUPPORT_STATIC_ALLOCATION */

configSUPPORT_STATIC_ALLOCATION は, config次第だが多分0(要確認)。

#ifndef configSUPPORT_STATIC_ALLOCATION
    /* Defaults to 0 for backward compatibility. */
    #define configSUPPORT_STATIC_ALLOCATION 0
#endi

スケジューラをタスクの1つとして登録する。

        /* The Idle task is being created using dynamically allocated RAM. */
        xReturn = xTaskCreate(  prvIdleTask,
                                configIDLE_TASK_NAME,
                                configMINIMAL_STACK_SIZE,
                                ( void * ) NULL,
                                portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
                                &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
    if( xReturn == pdPASS )
    {
        ...

        /* Interrupts are turned off here, to ensure a tick does not occur
        before or during the call to xPortStartScheduler().  The stacks of
        the created tasks contain a status word with interrupts switched on
        so interrupts will automatically get re-enabled when the first task
        starts to run. */
        portDISABLE_INTERRUPTS();

        #if ( configUSE_NEWLIB_REENTRANT == 1 )
        {
            /* Switch Newlib's _impure_ptr variable to point to the _reent
            structure specific to the task that will run first. */
            _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
        }
        #endif /* configUSE_NEWLIB_REENTRANT */

        xNextTaskUnblockTime = portMAX_DELAY;
        xSchedulerRunning = pdTRUE;
        xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;

        /* If configGENERATE_RUN_TIME_STATS is defined then the following
        macro must be defined to configure the timer/counter used to generate
        the run time counter time base.   NOTE:  If configGENERATE_RUN_TIME_STATS
        is set to 0 and the following line fails to build then ensure you do not
        have portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() defined in your
        FreeRTOSConfig.h file. */
        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

        traceTASK_SWITCHED_IN();

        /* Setting up the timer tick is hardware specific and thus in the
        portable interface. */
        if( xPortStartScheduler() != pdFALSE )
        {
            /* Should not reach here as if the scheduler is running the
            function will not return. */
        }
        else
        {
            /* Should only reach here if a task calls xTaskEndScheduler(). */
        }
    }
    else
    {
        ...
    }
  1. スケジューラのタスクの生成に成功したか(xReturn == pdPASS)を確認する
  2. xPortStartScheduler() を呼ぶまで割り込み禁止にする
        xNextTaskUnblockTime = portMAX_DELAY;
        xSchedulerRunning = pdTRUE;
        xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;
  • xNextTaskUnblockTime は、...
  • xSchedulerRunning は、スケジューラが動作中はpdTRUEvoid vTaskEndScheduler( void ) が呼ばれると pdFALSE に設定される
  • xTickCount は、次のスケジュールのタスクの割り込みまでの時間。
        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

        traceTASK_SWITCHED_IN();

この2つのマクロはdefaultはカラ。

        if( xPortStartScheduler() != pdFALSE )
  • port.c
BaseType_t xPortStartScheduler( void )
{
    /* Setup the relevant timer hardware to generate the tick. */
    prvSetupTimerInterrupt();

    /* Restore the context of the first task that is going to run. */
    portRESTORE_CONTEXT();

    /* Simulate a function call end as generated by the compiler.  We will now
   jump to the start of the task the context of which we have just restored. */
    __asm__ __volatile__ ( "ret" );

    /* Should not get here. */
    return pdTRUE;
}
//initialize watchdog
void prvSetupTimerInterrupt( void )
{
    //reset watchdog
    wdt_reset();

    //set up WDT Interrupt (rather than the WDT Reset).
    wdt_interrupt_enable( portUSE_WDTO );
}
  • prvSetupTimerInterrupt() は、WDT(Watch Dog Timer)をリセットし、WDT割り込みを有効にしている。 ** `wdt_rest(), wdt_interrupt_enable()' は要確認
  • portRESTORE_CONTEXT() は、CPUレジスタを戻して(pop)、xPortStartScheduler() が呼ばれる前の状態(context)に戻している。
  • ret を発行して関数から戻る。

ここまででスケジューラタスクの登録し、実行した状態になった。

次回は、少し戻ってスケジューラタスクの生成処理を読み解いてみる。

        /* The Idle task is being created using dynamically allocated RAM. */
        xReturn = xTaskCreate(  prvIdleTask,
                                configIDLE_TASK_NAME,
                                configMINIMAL_STACK_SIZE,
                                ( void * ) NULL,
                                portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
                                &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */