12_2 ボストンデータセットのロードとtf.Tensorオブジェクトへの変換

機械学習はデータがないと始まらないので、まずはボストンデータセットの読み込みから始めます。

以降では、「boston-housing」で使用されているコードを参考にしています。ただし、TensorFlow.jsのサンプルは一般に非常に難しく書かれていて、そのくせポイントが分かりづらいように思えるので、Node.jsやJavaScriptクラスの使用はやめ、できるだけ平易な書き方をするようにしています。

boston-housing」サンプルでは、CSVファイルの読み込みと解析にNode.jsのpapaparseモジュールが使用されているので、ここでも同様にPapa Parseライブラリを使用することにします。

 <script src="https://fastcdn.org/Papa-Parse/4.1.2/papaparse.min.js"></script>

次のJavaScriptコードを記述し、data.jsファイルとして保存します。

// https://github.com/tensorflow/tfjs-examples/blob/master/boston-housing/data.js

const BASE_URL = 'https://storage.googleapis.com/tfjs-examples/multivariate-linear-regression/data/';

const TRAIN_FEATURES_FN = 'train-data.csv';
const TRAIN_TARGET_FN = 'train-target.csv';
const TEST_FEATURES_FN = 'test-data.csv';
const TEST_TARGET_FN = 'test-target.csv';

const BOSTONDATA_NUMFEATURES = 12;

/**
 * 与えられたCSVデータを、数値の配列を含む配列として返す
 *
 * @param {Array<Object>} data ダウンロードしたデータ
 *
 * @returns {Promise.Array<number[]>} dataを浮動小数点数を持つ値として解析し解決する
 */
const parseCsv = async(data) => {
    return new Promise(resolve => {
        data = data.map((row) => {
            return Object.keys(row).map(key => parseFloat(row[key]));
        });
        console.log('CSVの解析終了');
        resolve(data);
    });
};

/**
 * CSVファイルをダウンロードして返す
 *
 * @param {string} filename ダウンロードするファイルの名前
 *
 * @returns {Promise.Array<number[]>} 解決すると解析されたCSVデータになる
 */
const loadCsv = async(filename) => {
    return new Promise(resolve => {
        const url = `${BASE_URL}${filename}`;
        console.log(`${url}からデータをダウンロード中...`);
        // Papa Parseライブラリ https://www.papaparse.com/
        Papa.parse(url, {
            download: true,
            header: true,
            complete: (results) => {
                console.log('ダウンロード終了');
                resolve(parseCsv(results['data']));
            }
        })
    });
};
/*

/**
 * フィッシャーイェーツのシャッフル アルゴリズムを使って、
 * データとターゲットを(関係性を維持しながら)シャッフルする。
 */
const shuffle = (data, target) => {
    let counter = data.length;
    let temp = 0;
    let index = 0;
    while (counter > 0) {
        index = (Math.random() * counter) | 0;
        counter--;
        // data:
        temp = data[counter];
        data[counter] = data[index];
        data[index] = temp;
        // target:
        temp = target[counter];
        target[counter] = target[index];
        target[index] = temp;
    }
};

つづいて、次のJavaScriptをnormalization.jsという名前で保存します。

// https://github.com/tensorflow/tfjs-examples/blob/master/boston-housing/normalization.js
/**
 * データ配列の各列の平均と標準偏差を計算する
 *
 * @param {Tensor2d} data 各列の平均と標準偏差を別々に計算するデータセット
 *
 *
 * @returns {Object} 各ベクトル列の平均と標準偏差を1dテンソルとして含む
 */
const determineMeanAndStddev = (data) => {
    // 各データの値の平均
    const dataMean = data.mean(0);
    // 各データの値と平均との差
    const diffFromMean = data.sub(dataMean);
    // 各データの値と平均との差の2乗
    const squaredDiffFromMean = diffFromMean.square();
    // 各データの値と平均との差の2乗の平均
    const variance = squaredDiffFromMean.mean(0);
    // 各データの値と平均との差の2乗の平均の平方根
    const dataStd = variance.sqrt();
    return {
        dataMean, dataStd
    };
}

/**
 * 与えられた期待平均値と標準偏差を使って、与えられたデータセットに対し
 * 平均を引き、標準偏差で割ることで、データセットを標準化する
 *
 * @param {Tensor2d} data: 標準化するデータ。 Shape: [batch, numFeatures].
 * @param {Tensor1d} dataMean: データの期待平均値。Shape [numFeatures].
 * @param {Tensor1d} dataStd: データの期待標準偏差。Shape [numFeatures]
 *
 * @returns {Tensor2d}: データと同じshapeで、平均が0、標準偏差が1になるように、
 * 各列が標準化されたテンソル
 */
const normalizeTensor = (data, dataMean, dataStd) => {
    return data.sub(dataMean).div(dataStd);
}

HTMLファイルには、data.jsとnormalization.jsファイルを読み込みます。メインのJavaScriptには、まず次のコードを記述します。これにより、Googleの提供する4つのCSVファイルをダウンロードして、数値をまとめた配列を4つの変数に割り当てることができます。

// Googleの提供する4つのCSVファイルをダウンロードして解析し、数値として配列に入れる。
const [trainFeatures, trainTarget, testFeatures, testTarget] =
await Promise.all([
    loadCsv(TRAIN_FEATURES_FN), loadCsv(TRAIN_TARGET_FN),
    loadCsv(TEST_FEATURES_FN), loadCsv(TEST_TARGET_FN)
]);

ここでは、data.jsに記述したloadCsv()関数にダウンロードするCSVファイルへのパスを渡して呼び出しています。loadCsv()関数では、Papa ParseライブラリのPapa.parse()によってCSVファイルがダウンロードされ解析されます。解析結果はその後parseCsv()関数に渡され、数値を要素として持つ配列の配列が返されます。

たとえば、変数trainFeaturesは、数値の要素を12個持つ、次のような配列を333個持つ配列です。
[0.32543, 0, 21.89, 0, 0.624, 6.431, 98.8, 1.8125, 4, 437, 21.2, 15.39]

また変数trainTargetは、数値の要素を1個持つ、次のような配列を333個持つ配列です。
[18]

メインのJavaScriptではその後、data.jsに記述したshuffle()関数を使って4つのデータを(関係性を保ったまま)シャッフルし、それぞれをtf.Tensorオブジェクトに変換します。

// シャッフルする。
shuffle(trainFeatures, trainTarget);
shuffle(testFeatures, testTarget);

// CSVからのデータをtf.Tensorオブジェクトに変換する
const trainFeaturesTF = tf.tensor2d(trainFeatures, [333, 12], 'float32');
const trainTargetTF = tf.tensor2d(trainTarget, [333, 1], 'float32');
const testFeaturesTF = tf.tensor2d(testFeatures, [173, 12], 'float32');
const testTargetTF = tf.tensor2d(testTarget, [173, 1], 'float32');

そしてその後が実は難しいのですが、扱うデータを標準化します。標準化にはデータの平均値と標準偏差と呼ばれるものが必要なので、それをnormalization.jsファイルに記述したdetermineMeanAndStddev()関数を使ってもとめ、normalizeTensor()関数で標準化します。

// trainFeaturesTFデータの各属性値の平均と標準偏差を計算する
let { dataMean, dataStd } = determineMeanAndStddev(trainFeaturesTF);
// 訓練用とテスト用のデータを標準化する。
const trainFeaturesNormalized = normalizeTensor(trainFeaturesTF, dataMean, dataStd);
const testFeaturesNormalized = normalizeTensor(testFeaturesTF, dataMean, dataStd);

本記事ではその後さらに、標準化したテスト用データを評価用とテスト用に分けています。

// 標準化したテスト用データをさらに、評価用とテスト用に分ける。
const [testFeaturesNormalized_evaluate, testFeaturesNormalized_test] = testFeaturesNormalized.split([170, 3]);
// テスト用の目的変数のデータも同様の比率で、評価用とテスト用に分ける
const [testTargetTF_evaluate, testTargetTF_test] = testTargetTF.split([170, 3]);

次回は、こんな機会でもなければ、ぼーっと生きていなくても一生出会うことはないだろうと思われるデータの標準化や標準偏差などについて見ていきます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA