わかりやすいデータ解析入門-C++による演習

Last Update: March 15 2018
  1. 内山俊郎 著(ムイスリ出版 2016.01.27発行)A5判, 248ページ, 2450円+税 ISBN:978-4-89641-244-4
  2. 概要:データ解析を学ぶ学生および実践者のための理論と実際の解析について解説・紹介.データマイニングについての基礎理論とデータ解析演習を用意. 必要なプログラムのソースコード,コンパイル方法,実行例を提示.
  3. [基礎編]1章:さまざまなデータ解析, 2章:データ解析のための基本操作, 3章:クラスタリング, 4章:識別関数の学習, 5章:確率論と確率モデル, [発展編:行列表現の利用]6章:特徴変換, 7章:行動ログデータの解析, 8章:文書データの解析, 付録A:演習問題解答例と各種データ, 付録B:参考ソースコード
  4. ソースコードのダウンロード
  5. 正誤表サポート情報
  6. 講義用ノート 1章2章補足C++入門1C++入門2, 3章4章5章7章補足


ソースコードのダウンロード

本書に含めたソースコードの多くは,数十行以内の短いものです. 理論式を理解することを意図していますので,入力することをお勧めします. しかし,入力ミスの原因がわからないなどの問題が起こりがちです. そこで,本書をご購入された方のために,ソースコードと本書に収録したデータを 用意しました(一部改良あり).ソースコードのダウンロード

正誤表

  1. p.15の「表2.1.emacsのコマンド」の一番下のブロックの下から5行目にある「次のバッファに移動する」に対応するコマンドが間違っていました.

    Ctrl-o

    次のバッファに移動する
    は間違いです.正しくは,

    Ctrl-x o

    次のバッファに移動する
    です.
  2. p.44の中程の表(数値)に間違いがありました.正しくは,下記です.
    \({\boldsymbol x}\) あるいは \({\boldsymbol \mu}\)\({\boldsymbol x}-{\boldsymbol \mu}\)\(\|{\boldsymbol x}-{\boldsymbol \mu}\|^2\)
    札幌市\((0.0, 0.0)\)\((-12.8, 7.3)\)\(12.8^2+7.3^2\)
    千歳市\((23.8,-26.4)\)\((11.0,-19.1)\)\(11.0^2+19.1^2\)
    江別市\((14.6, 4.6)\)\((1.8, 11.9)\)\(1.8^2+11.9^2\)
    重心\({\boldsymbol \mu}\)\((12.8,-7.3)\)
  3. p.99 9行目の「モデルパラメータ\({\cal F}\)」は, 「モデルパラメータ\({\cal Q}\)」でした.
  4. p.135 8行目の「実行ファイルreadTrpreadTrpCrpを生成・・」は, 「実行ファイルreadTrp, readTrpCrsを生成・・」のタイポです.
  5. p.177 式(8.20)
    \[ J\!S_W = \frac{1}{N}\sum_{k=1}^K N_k \sum_{m=1}^M -q_m^k \log q_m^k -\left(-\frac{1}{N}\sum_{i=1}^N \sum_{m=1}^M -p_m^i \log p_m^i \right),\]

    は,第2項の負号が1つ余計で,正しくは,
    \[ J\!S_W = \frac{1}{N}\sum_{k=1}^K N_k \sum_{m=1}^M -q_m^k \log q_m^k -\left(\frac{1}{N}\sum_{i=1}^N \sum_{m=1}^M -p_m^i \log p_m^i \right),\]

    です.

  6. p.195 式(8.40)
    \[ \prod_{i=1}^N A^i\prod_{m=1}^M (q_m^k)^{x^i_m}, \]

    は,クラスタ毎に同時確率を計算すべきで,正しくは,
    \[ \prod_{k=1}^K \prod_{{\boldsymbol x}^i \in C^k} A^i \prod_{m=1}^M (q_m^k)^{x^i_m}, \]

    です.

  7. シェルスクリプトに特定のシェル(bash)に依存するコードがあり,訂正し非依存にします(バッククォート「`」を使います).

サポート情報

ストップワード除去における空の文書除去

8章の「文書データの解析」では,実験用データとしてニュース投稿記事 20Newsgroupsをご紹介しました.そして, p.161〜p.164にかけてストップワードを除去する処理を示し, p.164の4-6行目では「中身が空の文書ができる可能性があります.そのような場合は, 空の文書を取り除いて,文書idを詰めるなどの処理をした方がよいでしょう.」 と書きました.執筆当時は気づかなかったのですが,20Newsgroupsのデータでも ストップワードを除去することで,学習用データとテスト用データにそれぞれ1 つずつ「空の文書」が出現します.文書idを詰めなくても,それ以降の 処理プログラムは動きます.しかし,空の文書がクラスタリングや分類問題の 評価結果に影響するので,取り除く方法を示します.注意すべきは,文書が 属するクラスラベル情報も連動して直すことです.さもないと ボタンの掛け違いで,ひどいことになります.

例えば,空の文書を含む文書データ(3つ組)20ngNoS.datと クラスラベル情報20ng.labelがある場合,下記 reallocateDocId.cpp

g++ -std=c++11 -O3 -I/usr/include/eigen3 reallocateDocId.cpp -o reallocateDocId
のようにしてコンパイルし,変更後の文書データの出力先を 20ngNoSid.dat,クラスラベル情報の出力先を 20ngNoSid.labelとする場合,
./reallocateDocId 20ngNoS.dat 20ng.label 20ngNoSid.label > 20ngNoSid.dat
のようにすれば,クラスラベル情報の修正も含めて空の文書 のidの除去ができます.
//reallocateDocId.cpp
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <set>
#include <sstream>
#include <Eigen/Dense>
#include <Eigen/Sparse>
using namespace Eigen;
using namespace std;
int main(int argc, char* argv[]){
  string fname   = argv[1]; // 文書疎行列(3つ組)ファイル
  string fnameC  = argv[2]; //(クラスラベル)ファイル入力
  string fnameC2 = argv[3]; //(クラスラベル)ファイル出力
  int nRows = 0, nCols = 0; // 行数(ndim)と列数(文書数)を表す変数の宣言
  int row, col;
  double val;
  string buf;
  set<int> validDocIds;     // 0始まり.有効な文書idの集合
  typedef Triplet<double> T;
  vector<T> triplets;
  ifstream ifile( fname );
  while( getline(ifile,buf) ){
    istringstream iss(buf);
    iss >> row >> col >> val;
    triplets.emplace_back(T(row-1,col-1,val));
    if( row > nRows ) nRows = row;
    if( col > nCols ) nCols = col;
    validDocIds.insert(col-1); // 文書idを有効な文書id集合に追加
  }
  ifile.close();
  int ndim = nRows;    // 行数は,単語種類数(特徴の次元数ndim=M)を表す
  int nvec = nCols;    // 列数は,文書数nvec=Nを表す
  SparseMatrix<double> spX(ndim,nvec);
  spX.setFromTriplets(triplets.begin(), triplets.end());
  ifstream ifileC( fnameC );
  vector<int> vecC;
  while( getline(ifileC,buf) ) vecC.emplace_back(stoi(buf)-1);
  ifileC.close();
  int Did = 0;
  ofstream ofileClass( fnameC2 );
  for( int i = 0 ; i < nvec ; i++ ){                // すべての文書idについて
    if( validDocIds.find(i) != validDocIds.end() ){ // 空文書でない有効idなら
      ofileClass << vecC[i]+1 << endl;
      for(SparseMatrix<double>::InnerIterator it(spX,i);it; ++it)
	cout << it.row()+1 << " " << Did+1 << " " << it.value() << endl;
      Did++;
    }
  }
}