サンプル・プログラムの実行例
学習データにある期間中、@papa_pahoo は「井の頭恩賜公園でお花見する」とツイートはしていないが、他のアカウント比べると、この発言をする可能性が比較的高いというのが単純ベイズ分類機の判定結果である。
サンプル・プログラム
BayesClassifier.php | サンプル・プログラム |
pahooTwitterAPI.php | Twitter APIを利用するクラス pahooTwitterAPI。 使い方は「PHPでTwitterに投稿(ツイート)する」などを参照。include_path が通ったディレクトリに配置すること。 |
ベイズの定理
ベイズの定理は高校数学の範疇だが、ここで少し振り返っておこう。
左のベン図を見れば一目瞭然だが、Aが起きる確率、すなわちXもYもともに起きる確率は、 と から求めることができる。
同様にY側から考えると、
となり、次の等式が成立する。これがベイズの定理だ。
ベイジアン・フィルターを含めた機械学習では、 を求めることになるので、式を次のように変形しておく。
単純ベイズ分類機
あるテキスト が与えられたとき、それがユーザー のものである確率を とする。ベイズの定理の が に、 が に置き換わったと考えればよい。
となる。
ここで、 はどのユーザーにおいても一定値であるから、
と表せる。
すなわち、 と の2つの確率が計算できればよい。
まず であるが、これは、
とあらわせる。ここで、 は学習した全ツイート数、 は当該ユーザーのツイート数である。
次に であるが、テキスト は単語の集合である。単語が文書内にどこに出てくるかは考慮しない(これを bag-of-words と呼ぶ)と仮定すると、
とあらわせる。
bag-of-words を仮定する方式を、単純ベイズ分類機(ナイーブ・ベイジアン)と呼ぶ。
ここで はきわめて小さな値であり、ツイートに多くの単語が含まれていると、乗算の結果が限りなくゼロに近づき、コンピュータの演算処理の中でアンダーフローを起こす恐れがある。そこで、対数をとって、乗算を加算に変更する。
判定(推定)では、複数の に対して を計算し、その結果が最大になる を求める。
サンプル・プログラムの流れ
単純ベイズ分類機
0915: // [3] 判定(単純ベイズ分類機) ==============================================
0916: /**
0917: * ユーザーの生起確率
0918: * @param string $screen_name スクリーンネーム
0919: * @return float 生起確率/FALSE
0920: */
0921: function userProb($screen_name) {
0922: $sql_total = 'SELECT count(*) FROM ' . $this->table_tweets . ' WHERE 1';
0923: $sql_count = 'SELECT count(*) FROM ' . $this->table_tweets . ' WHERE user_id=:user_id';
0924:
0925: list($userID, $dt) = $this->getUserID($screen_name);
0926: $total = 0;
0927: $count = 0;
0928: if ($userID != FALSE) {
0929: //合計
0930: $stmt = $this->pdo->prepare($sql_total);
0931: $stmt->execute();
0932: $row = $stmt->fetch();
0933: $total = $row[0];
0934:
0935: //生起回数
0936: $stmt = $this->pdo->prepare($sql_count);
0937: $stmt->bindValue(':user_id', $userID, PDO::PARAM_STR);
0938: $stmt->execute();
0939: $row = $stmt->fetch();
0940: $count = $row[0];
0941: }
0942:
0943: return ($total == 0) ? FALSE : ($count / $total);
0944: }
0945:
0946: /**
0947: * あるユーザーにおける単語の出現頻度
0948: * @param string $screen_name スクリーンネーム
0949: * @param string $word 単語
0950: * @return float 出現頻度/FALSE
0951: */
0952: function wordProb($screen_name, $word) {
0953: $sql_total = 'SELECT * FROM ' . $this->table_tweets . ' WHERE user_id=:user_id AND flag=1';
0954: $sql_count = 'SELECT count FROM ' . $this->table_vectors . ' WHERE user_id=:user_id AND word=:word';
0955:
0956: list($userID, $dt) = $this->getUserID($screen_name);
0957: $total = 0;
0958: $count = 0;
0959: if ($userID != FALSE) {
0960: //合計
0961: $stmt = $this->pdo->prepare($sql_total);
0962: $stmt->bindValue(':user_id', $userID, PDO::PARAM_STR);
0963: $stmt->execute();
0964: $res = $stmt->fetchAll();
0965: $total = 1;
0966: foreach ($res as $val) $total++;
0967:
0968: //出現回数
0969: $stmt = $this->pdo->prepare($sql_count);
0970: $stmt->bindValue(':user_id', $userID, PDO::PARAM_STR);
0971: $stmt->bindValue(':word', $word, PDO::PARAM_STR);
0972: $stmt->execute();
0973: $res = $stmt->fetchAll();
0974: $count = 1; //ラプラススムージング
0975: foreach ($res as $val) $count += $val['count'];
0976: }
0977:
0978: return ($total == 0) ? NULL : ($count / $total);
0979: }
0980:
0981: /**
0982: * あるユーザーにおけるスコア計算
0983: * @param string $screen_name スクリーンネーム
0984: * @param string $text テキスト
0985: * @return float スコア/NULL
0986: */
0987: function textProb($screen_name, $text) {
0988: $words = array();
0989:
0990: //ユーザーの生起スコア
0991: $score = $this->userProb($screen_name);
0992: if ($score == FALSE) return NULL;
0993: $score = log($score);
0994:
0995: //単語の出現スコア
0996: $this->getWords($text, $words);
0997: foreach ($words as $word) {
0998: if (! $this->isword($word)) continue;
0999: $ret = $this->wordProb($screen_name, $word[0]);
1000: if ($ret == FALSE) return NULL;
1001: $score += log($ret);
1002: }
1003:
1004: return $score;
1005: }
1006:
1007: /**
1008: * 単純ベイズ分類機
1009: * @param string $text 判定したいテキスト
1010: * @param array $users ユーザー別判定結果を格納する配列
1011: * @return なし
1012: */
1013: function resultsClassified($text, &$users) {
1014: $sql_select = 'SELECT * FROM ' . $this->table_users . ' WHERE active=1';
1015:
1016: $stmt = $this->pdo->prepare($sql_select);
1017: $stmt->execute();
1018: $key = 0;
1019: while ($row = $stmt->fetch()) {
1020: $score = $this->textProb($row['screen_name'], $text);
1021: foreach ($users as $key=>$user) {
1022: if ($user['screen_name'] == $row['screen_name']) {
1023: $users[$key]['score'] = $score;
1024: break;
1025: }
1026: }
1027: }
1028: }
1029:
1030:
1031: // End of Class ==============================================================
参考サイト
- PHPで機械学習(その1):ツイートを取得しDB格納:ぱふぅ家のホームページ
- PHPで機械学習(その2):ツイート内容を学習:ぱふぅ家のホームページ
- PHPで機械学習(その3):単純ベイズ分類機:ぱふぅ家のホームページ
- PHPとデータベース:ぱふぅ家のホームページ
- PHPでTwitterに投稿(ツイート)する:ぱふぅ家のホームページ
- 機械学習とは:sas
- ベイズの定理:高校数学の美しい物語
- ベイズの定理のおさらい:T_NAKAの阿房ブログ
- 4 情報と確率(ベイズの定理):麻雀概論
- オッズとベイズの定理:てら58
今回は、学習させた結果をもとに、あたえられたテキストが、どのユーザーのツイートであるかを推定するプログラムを完成させる。
機械学習にはいくつかのアルゴリズムがあるが、ここでは実装が簡単な単純ベイズ分類器を用いることにする。学習方法は簡単で、ユーザー別の単語の出現回数をデータベースに登録していけばいい。
(2021年7月11日)PHP8対応,リファラ・チェック改良