C/C++のライブラリについて

概要

  • 静的ライブラリ,動的ライブラリ(そんなものは無い.それは一般的には共有ライブラリ),共有ライブラリ,静的リンク,動的リンクなど,分かっているようで理解が曖昧な点を整理しておく.
  • C++ではヘッダファイルだけで提供するライブラリがある.ヘッダオンリーライブラリなどと呼ばれているようだ.それも簡単なメモを書いておく.

静的ライブラリと共有ライブラリ

  • 拡張子はそれぞれ,静的ライブラリは.a,共有ライブラリは.so.
  • コンパイル時の-lで指定するのは静的ライブラリも共有ライブラリも同じ.例えば,-lmはmathライブラリがリンクされる.ただし,デフォルトでは,まず共有ライブラリ(libhoge.so)を探して,見つかれば共有ライブラリがリンクされる.共有ライブラリがなければ,静的ライブラリ(libhoge.a)を探してリンクする.明示的に静的ライブラリをリンクしたい場合は,-staticオプションを付ける.
  • 静的ライブラリは単純には複数のオブジェクトファイルをまとめたもの.arコマンドで生成する.
  • 共有ライブラリは各オブジェクト生成時に-fPIC(Position Independent Code)を指定して,ライブラリ生成時に-sharedを指定する.

静的リンクと動的リンク

  • 静的リンクはライブラリがオブジェクトファイルに埋め込まれる.よって,ライブラリが無い環境でも動くバイナリを生成できる.しかし,ファイルサイズは大きくなる.
  • 反対に,動的リンクはオブジェクトファイルにはライブラリ自体は埋め込まれず,そこに埋め込めという情報だけが埋め込まれる.これは,ファイルサイズは小さくなるものの,ライブラリが無い環境では動かない.マシン上の複数のプログラムが同じ共有ライブラリを使う場合にもメモリの削減になる.libhoge.dllはダイナミックリンクライブラリであり,名前の通り実行時にリンクされる.
  • 共有ライブラリのリンクの際にライブラリを探索するパスはLD_LIBRARY_PATHである.共有ライブラリを使う場合は,このパスに対象のディレクトリが含まれていることを確認する.
  • 動的リンクは実行時に配置されるので,配置に依存しないコンパイル(PIC)をしておく

具体例

  • 自分のユーティリティライブラリ(myutil)を作る場合
///////////////////////////////////////////////////////////
// myutil.h
void hello_world(void);

///////////////////////////////////////////////////////////
// myutil.c
#include <stdio.h>
#include "myutil.h"

void hello_world(void) {
  printf("Hello world!! I'm a static library\n");
}

///////////////////////////////////////////////////////////
// main.c
#include <stdio.h>
#include "myutil.h"

int main()
{
  hello_world();
  return 0;
}

静的ライブラリとして使う場合

  • オブジェクトファイルを普通に作って,arでまとめて,リンクする.
$ gcc myutil.c -c             # myutil.oを生成
$ ar r libmyutil myutil.o     # libmyutil.aを生成
$ gcc main.c -L ./ -l myutil  # libmyutil.aをリンクしてコンパイル (libmyutil.soがない前提)
$ gcc -static main.c -L ./ -l myutil  # libmyutil.aをリンクしてコンパイル (libmyutil.soがある場合は明示)

共有ライブラリとして使う場合

  • -fPICで位置非依存なコードを作って,sharedでリンク
$ gcc -fPIC myutil.c -c                # myutil.oを生成
$ gcc -shared -o libmyutil.so myutil.o # libmyutil.soを生成
$ gcc main.c -L ./ -l myutil           # libmyutil.soをリンクしてコンパイル 

ヘッダオンリーライブラリ

  • ライブラリ提供者はヘッダファイルに全てを書いておいて,ライブラリ使用者はそのヘッダファイルを読み込む.
  • 毎回ヘッダファイルを読み込むのでコンパイル時間の増加の問題があるが,プリコンパイル済みヘッダファイルの利用によって避けることができる.
  • グローバル変数やスタティッククラス変数を定義しようとすると,複数のファイルが同じヘッダファイルを読み込むと多重定義エラーが発生する.避けるには,スタティックメンバ関数の中にスタティック変数として定義して,それを返す.

common lispプログラミング

Common lispの勉強をしていて出会ったことを書き連ねていく.

コーディングスタイル

  • 詳細を知りたい場合はGoogle Common lisp Coding規約を読む.
  • ファイル名はhoge-foo.lispという感じでハイフンでつなぐ。
  • グローバル変数は"hoge"
  • 定数は+hoge+
  • 大文字と小文字の違いはない。プログラムは小文字で書くことがほとんど。
  • 複数行のコメントは#| ... |#.

シンボルとキーワード

  • アトムはシンボルとシンボル以外に分類される。
    • シンボル以外は評価するとそれ自身を返す。例えば数値10とか文字列"hoge"とか。
    • シンボルは評価されると,それが指すものを返す。例えば,x=3のときにxを評価すると3が返る。リテラルはシンボルって考えれば良いのか?
  • +, sin, xなどはシンボルである。シンボルは評価するとシンボルが指すものに評価される。シンボル自身が欲しい場合はクォートする.
  • キーワードとは,評価すると自分自身になる特集なシンボル。
  • リーダは:で始まるシンボルを見ると,シンボルを登録する際にそのシンボルが指すものとして,そのシンボル自身を登録する。

format

  • ~がCで言う%(指示詞)
  • ~%は改行。~5%は5行改行。
  • ~Dは整数(大文字Dでも小文字dでも変わらない)
  • ~Aは文字列でも数値でも良きにやる.
  • 指示子には個数を設定できる.~10tは10カラム分のタブ.
  • ~{...~}は1つのリストを消費して,リスト内の各要素を~{...~}の中の指示子で消費していく.
  • print-prettyで改行とかを良きにやってくれる.
;; ~{...~}の例
(format t "~{~d ~d ~d~} ~a" '(1 2 3) :hoge)

print/princ/prin1/pprint

  • prin1 : readし易い形。
  • print : 一度改行を出力してからprint1。
  • princ : 人向け(エスケープ文字出さないとか)
  • pprint: printを読みやすくしたもの(連続スペースを1つに変換)

print1-to-string/write-to-string

  • xxx-to-stringはストリームじゃなくて,文字列として出力する。
  • writeはprin1よりも原始的で,print1はwriteの一部のオプションが設定されたもの。

破壊的代入

  • INCF, DECF, PUSH, POPなどは破壊的な代入操作をする.モディファイアマクロと呼ぶらしい.
  • 便利なマクロとして,x,yの値をswapするマクロ:ROTATEFがある.PUSH,POPも破壊的.
(defvar *x* 1)
(defvar *y* 2)
(incf *x*)        ; (setf *x* (+ 1 *x*))
(decf *x*)        ; (setf *x* (- 1 *x*))
(incf *x* 10)     ; (setf *x* (+ 10 *x*))
(rotatef *x* *y*) ; swap(x,y)

defunの変数は値渡し

  • defunで渡される引数は値渡し(ポインタじゃなくて,参照している値が渡される).
  • だから,当然だけど変数を引数で渡して,defunの中でその変数を更新するようなことは間違い.
  • ポインタ(consセル)を指している変数を渡すと,それはそのポインタのアドレスを渡していることになるから,その先の変数を変更できる.
;; 間違い
(defparameter *x* 0)
(defun hoge (x))
   (setf x 1))
(hoge *x*)        ; *x*は更新されない
(setf *x* (hoge)) ; 更新したければ返り値を使う

(defparameter *lst* '(1 2 3 4 5))
(defun foo (lst)
   (setf (car lst) 0))
(foo lst)         ; (0 2 3 4 5) 更新される

リスト処理

  • consやappendは元のリストは破壊しないので注意.
  • リストに要素を追加したい場合は,明示的にconsしたものをsetfするか,PUSHを使う(取り出すときはPOP) .
  • sortすると元の配列が壊される.しかもsortされる訳でもない?サイズが変わったりもしてる.なぜ?
  • リストをシフトしたいときはalexandriaのrotate.引数で右も左もシフトできる.
(setf lst (loop for x from 0 to 100 collect x))
(sort lst #'>)
lst ; ソートされたものの一部が入っている?

Array

  • 可変長配列(:adjustableを指定すれば)
  • make-arrayで作って,arefで参照して,setf+arefで代入する。
;; 配列生成
(defparameter *arr* (make-array 3)) ; => #(0 0 0)
(aref *arr* 0) ; => 0

;; 初期値指定
(defparameter *arr* (make-array 3 :initial-element 5)) ; => #(5 5 5)

VectorとArray

  • VECTORは固定長,ARRAYは可変長であり,多次元も可能.
  • 可変長なARRAYを作りたい場合は,:fill-pointerと:adjustableを設定する.:fill-pointerは現在の末尾の要素番号を指す整数.可変長のARRAYに要素を追加する場合はvector-push-extendを使う(vector-pushじゃない)
  • VECTORにもmapを適用できる.(map 'vector #'func array)形式.
(defparameter *varr* (make-array 0 :fill-pointer 0 :adjustable t))
(vector-push-extend 0 *varr*)   ; *varr* = #(0)
(vector-push-extend 1 *varr*)   ; *varr* = #(1 0)
(vector-push-extend 2 *varr*)   ; *varr* = #(2 1 0)
(map 'vector #'print *varr*)

ハッシュ

  • 文字列をKeyにするときは,:test 'equalを指定する.デフォルトはeqlになってシンボルを想定している.
  • loopでループする時は独特なので覚えてしまう.
(defparameter *hash* (make-hash-table :test 'equal))
(setf (gethash "hoge" *hash*) 1)
(setf (gethash "hogehoge" *hash*) 2)
(setf (gethash "hogehogehoge" *hash*) 3)
(gethash "hogehoge" *hash*)
(loop for k being the hash-key in *hash* collect k)
(loop for v being the hash-value in *hash* collect v)

setfとsetqの違い

  • setqよりもsetfの方が賢い.
  • setqは第一引数を必ずQuateする.
  • setfは第一引数が必要なら評価して,不要なら評価しない.

eltとarefとnthの違い

  • eltはシーケンス全般に使えるが,nthはリスト,arefはアレイ(ベクター)のみに使える.文字列もベクタなのでarefが使える.
  • eltは汎用なので型チェックが動的に行われるが,nth/arefはそれが無いのでnth/arefの方が速い.
  • 速度差があると言われているけれど,sbclだと違いわからなかった.測定ミス???

defvarとdefparameterの違い

  • 使い方の違いとして、defvarはプログラムによる変数の操作、defparameterはプログラムに対する変数の操作。
  • defvarは既に定義済みの変数の場合には再定義しない。
  • defparameterは定義済みの有無に関わらず再定義する。
  • defparameterは初期値が必ず必要.初期値が無い場合は:unboundを与える.

nullとendpの違い

  • nullはnilの時にTで,それ以外は文字列でもアトムでもFNILを返す.
  • endpはリスト専用で,アトムや文字列を入れるとエラーを発生する.

funcallとapply

  • funcallは引数をリストにまとめて渡す、applyはリストを渡す。
(funcall #'+ 2 3 4)
(apply #'+ '(2 3 4)

リーダマクロ

  • (quote (1 2 3)) -> '(1 2 3)
  • (function hoge) -> #'hoge これは関数名から関数の本体を得る方法。つまり関数のシンボルhogeが指すものを得る。
  • "#+","#-"は処理系依存コードなどを書く目的で,#+は式が真のときに読み込まえて,#-は偽の時に読み込まれる。どんな処理系でも処理系のシンボルは定義されている。例えばSBCLなら:sbclが定義されている.これを使って,処理系によって分岐するコードは下記になる. 例) #+:sbcl (expr for sbcl case) #+:ccl (expr for clozure cl case) #-(or :sbcl :ccl) (expr unknown case)
  • Vector/StructはそれとReaderがわかるようにprintでもサフィックスがつく。printで出力して,それをreadで読みこめば,そのままベクタ,構造体として扱える.
    • vector: #(1,2,3)
    • struct: #S(struct-name (:slot-name ...)* )

functionが不要?

  • 下のコードが動くのはなんで?"#"は要らないの?
(defun square (x)
  (* x x))
(mapcar #'square '(1 2 3 4 5)) ; (1 4 9 16 25) 
(mapcar 'square '(1 2 3 4 5))  ; (1 4 9 16 25) なんで???

ループ

do系

  • doは複雑なように見えて実はCのforと大きくは変わらない.初期値;更新式;終了条件;ループ本体,って感じだ.
;; リストでのループ
(dolist (v '(1 2 3 4 5 6 7 8 9))
  (print v))
;; 指定回数のループ
(dotimes (i 10)
  (print i))
;; do
;; (do ((変数, 初期値, 更新式)*) 
;;     (終了判定式 返り値)
;;     (body*))

loopマクロ

;; loop in list
(loop for x in '(a b c d e)
     do (print x))
;; with-when
(loop for d in '(0 1 2 3 4 5 6 7 8 9)
   when (evenp d)
   do (print d))
;; with if
(loop for d in '(0 1 2 3 4 5 6 7 8 9)
   if (evenp d)
   do (print d))
;; with while
(loop for d in '(0 1 2 3 4 5 6 7 8 9)
   while (< d 5)
   do (print d))

;; Counting
(loop for x from 0 to 10 by 2
     do (print x))

;; repeat
(loop repeat 5
     do (print 'hoge))

;; assign and update
(loop for x1 = 0 then x2
     for x2 = 1 then (+ x1 x2)
     while (< x1 30)
     do (print x1))

;; iota
(loop for x from 0 to 10 collect x)
(setf lst '(1 2 3 4 5))
(loop
   for x in lst
   for y in (cdr lst)
     do (format t "x:~d y:~d~%" x y))

#'(lambda ...)の意味

  • lambda式は関数オブジェクトを返すから,#'は不要なんじゃないの?と思うけど,下のような例が見られる.
(mapcar #'(lambda (x) (* 2 x)) '(1 2 3 4 5))
(mapcar (lambda (x) (* 2 x)) '(1 2 3 4 5))   ; これじゃダメ?

これを評価してみると上の式でも下の式でも同じようになる. Webを見ていると,lambda自身がマクロで,(function (lambda ... ))に展開される.すでに#'されている場合と違う場合で展開を分けているのかな?

構造体(defstruct)

  • Cで言うところのstructというよりもclassに近い。
  • コンストラクタとアクセッサが自動で生成される。
    • コンストラクタ:make-<struct_name>
    • アクセッサ  :<struct_name>-<slot_name>
    • 述語     :<struct_name>-p
(defstruct my-struct
    (slot1 nil) ; (スロット名 初期値)
    (slot2 nil))
(setf x (make-my-struct)) ; コンストラクタ
(my-struct-slot1 x)       ; アクセッサ

ディレクトリ操作

  • カレントディレクトリの確認 (truname "./") #=> 処理系非依存 (sb-posix:getwd)
  • カレントディレクトリ移動(loadでのデフォルトパスネームは変わらない) (ccl::cd #p"/hoge/hoge/") # CCL (sb-posix:chdir #p"directory-path")
  • デフォルトのパスネームを変える (setf default-pathname-defaults #p"hogehoge")

乱数

  • common lispでの乱数の生成方法.
  • 一様乱数はrandで生成できる.
  • Alexandriaにはリストをシャッフルしたり正規分布からの抽出などの高機能なものもある。
  • TODO: 正規分布などの確率分布からのサンプリング方法
(alexandria:shuffle '(1 2 3 4 5)) ; 破壊的操作なので注意

リストからのランダム選択

(nth (random (length lst)) lst)

一様乱数

  • 仕様で規定されているようだけど,実装は規定されていない.
  • (random N) -> N以下の数をランダムに出力。Nが整数なら整数,少数なら少数を出力する。
  • 乱数はrandom-stateに従って生成される。random-stateは常に更新され続けるので,乱数の種を渡すときはrandom-stateを設定すればいい。(make-random-state state)で設定。
  • 各処理系の実装は下記
    • SBCL, CMUCL, ECL:MT19937 周期は219937
    • Clisp:Linear COngruential Generator 周期は264
    • CCL:MRG31k3p(Combined multiple recursive generator) 周期は2185
  • CCLでメルセンヌ・ツイスタを使いたい場合などは,mt19937パッケージを使う。CMUCLの実装をポータブルにしたものらしい。 (ql:quickload :mt19937) (mt19937 N)で同じことができる。

多値

  • 多値を返す時はvalues,それをmultiple-value-bindで受け取る.
  • 多値はリストじゃないことに注意.
  • リストで受け取りたいときはmultiple-value-listで受け取る.
  • multiple-value-bindでバインドされる変数のスコープはmultiple-value-bindの中だけなことに注意.

文字列の数値への変換

  • 整数への変換ならparse-integer.
  • 有理数浮動小数点への変換も含むなら,read-from-string.
(parse-integer "3")
(read-from-string "3.0")

ファイル

  • openは使わずにwith-open-fileでやる.
  • printとreadの対称性を使う.printはS式をreadが読み込み得る形式で書き出す.
;; 書込テンプレート
(defun save-file (filename)
  (with-open-file (fout filename
            :direction :output
            :if-exists :supersede)
    (with-standard-io-syntax
      (print *data* fout))))

行ごとの処理

  • 行毎に処理したい場合はwrite-lineとread-lineの対称性を使う.
  • read-lineは改行までを読み込んで,改行文字を削除した文字列を返す.
  • read-lineは多値を返す関数で,2つ目に終端が改行文字で終わってない場合はtを,改行文字の場合はnilを返す.
  • 第2引数がファイル末尾に達した時にエラーを返すか否か.デフォルトがtになっているので,nilを入れてエラー発生しないようにする.
;; file
(with-open-file (fout "./tmp.dat" :direction :output)
  (write-line "hogehoge" fout)
  (write-line "foo" fout)
  (write-line "bar" fout))

(with-open-file (fin "./tmp.dat" :direction :input)
  (loop for line = (read-line fin nil)
       while line
       do (format t "~s~%" line)))

ファイルのロード(load)

  • loadすると、そのファイルを読み込んでトップレベルを評価する。
; hogeディレクトリのfoo.lispをロードする
(load "hoge/foo.lisp")

インデックスのセットの要素をリストから除去する

C++の場合

リストvがあって,そのリストから除去したいインデックスの集合remove_id_set(3,5,2)とかで指定する要素番号の要素を除去したい.eraseすると要素番号が一つずれるので,それも考慮して一旦集合をソートして,前から順に削除していくという方法.

#include <vector>
#include <random>
#include <algorithm>
#include <iostream>

std::random_device rnd;
std::mt19937 mt(rnd());

std::vector<int> generateUniqueRandomIntegers(size_t max, int num_random_number)
{
  std::vector<int> v(max);
  std::iota(v.begin(), v.end(), 0);
  std::shuffle(v.begin(), v.end(), mt);
  v.erase(v.begin()+num_random_number, v.end());
  return v;
}


std::vector<int> eraseSetOfIndices(std::vector<int> &erase_set, std::vector<int> &v)
{
  std::sort(erase_set.begin(), erase_set.end());
  int i=0;
  for (auto index : erase_set) {
    auto it = v.begin();
    v.erase(it+index-i);
    ++i;
  }
  return v;
}


int main()
{
  std::vector<int> v(10);
  std::iota(v.begin(), v.end(), 0);

  // remove index
  std::vector<int> remove_id_set = generateUniqueRandomIntegers(9,3);
  for (auto d : remove_id_set)
    std::cout << d;
  std::cout << " are removed" <<std::endl; 

  std::vector<int> ret = eraseSetOfIndices(remove_id_set, v);
  for (auto d : ret)
      std::cout << d;
  std::cout << std::endl; 

  return 0;
}

N以下の重複しない乱数(整数)の生成

C++

良い方法かは分からないけどiotaやeraseを使った例を見つけたのでメモ.やり方は一旦iotaで[0,1,...max]の配列を作って,それをシャッフルして,eraseで必要ない部分を消す.eraseはイテレータを2つ渡すと,開始位置から終了位置の1つ前までを消去できる.

#include <random>
#include <vector>

std::random_device rnd;
std::mt19937 mt(rnd());

std::vector<int> generateUniqueRandomIntegers(size_t max, int num)
{
  std::vector<int> v(max+1); // CAUTION "max+1" !!
  std::iota(v.begin(), v.end(), 0);
  std::shuffle(v.begin(), v.end(), mt);
  v.erase(v.begin()+num, v.end());
  return v;
}

Python

同じやり方をpythonでやる場合.sampleでシーケンスからの重複しないサンプリングが出来るのでそれを使う.

import random
def generateUniqueRandomNumbers(max, num):
    return random.sample(xrange(max+1), num)

C/C++からPythonを使う

要旨

 コンパイル方法

  • 色々なライブラリやインクルードパスを指定する必要がある.それらを確認するためのツールがpython-config.MacOSだとframework pythonとかで一式いけるみたいだ.全部は要らない場合もあるから,確認してからCMakeList.txt作るのも良いかもしれない.
gcc hoge.cpp $(python-config --cflags) $(python-config --labs)

使い方(1)

  1. Py_Initialize()でPythonインタプリタを初期化.
  2. PyRun_SimpleStirng(pythonコード文字列)でpythonコードを実行する.
  3. Py_Finalize()でインタプリタを終了.
#include <cstdio>
#include <Python.h>

int main(void)
{
  Py_Initialize();
  PyRun_SimpleString("print 'hello world'");
  Py_Finalize();
    
  return 0;
}

使い方(2)

上の例は実際はあまり使わない.使い方としては既存のスクリプトの中の関数を実行するというシーンが多いはず.これはC->Pythonの例だけど,Python->Cの例も実はほとんど変わらない.やっていることは(1)データの変換,(2)外部での実行,そして(3)データの逆変換.C->PythonPython->Cでは順番が逆なだけ. 1.

  • その他メモ

  • pythonとcどっちがトップ(どっちからどっち叩くんだろう?)なんだろう?
  • topはmainモジュール

c++のスレッド

 メモ

  • c++11からはスレッドが言語に取り込まれている.
  • コンパイルには-pthreadを付ける.
  • スレッドを一時的に止めるときにはsleepではなく,スレッド用のstd::this_thread::thread_for()を使う.
  • thread::join()をちゃんと実行して対象のスレッドの終了を待つようにする.(またはdetachする.detachはスレッドを管理対象から外す.但し実行は裏で勝手に続く.これどんな時に使うんだろう?)
  • 無限ループに入れるような場合は,外から止められるように定期的に停止フラグを見に行かせる.
  • std::threadのコンストラクタに関数を渡すことでその関数を別スレッドとして実行する.関数の引数はコンストラクタの第2引数以降で渡す.
  • クラス内でメンバ関数をスレッド化するときは特殊なので注意.リファレンスで渡すこと,第2引数にthisが要ることを忘れちゃダメ
#include <iostream>
#include <thread>
#include <unistd.h>

bool stop_flg = false;

void worker(int i, int j) {
  while (1) {
    std::cout << "I'm " << std::this_thread::get_id() << " thread." << std::endl;
    std::cout << i+j << std::endl;
    if (stop_flg)
      break;
  }
  return;
}


int main(int argc, char* argv[])
{
  std::thread th(worker, 1, 2);
  sleep(2);

  stop_flg = true;
  th.join();

  return 0;
}

情報量関連のメモ

概要

機械学習とか統計を勉強していると情報量とかエントロピーの話が出てきていつも復習をし直しているので,纏めておく.

全体の1行まとめ

各量の1行まとめ

  • 事象eの確率がP(e)で与えられるとき,その事象の情報量は-log(P(e))。不確実性の指標になる。
  • (情報)エントロピー(H)は,情報源の平均情報量,つまり,情報量の期待値=エントロピー。不確実性の期待値。
  • 情報源が2つ以上の場合には,一方(x)について知った場合の,もう一方(Y)のエントロピー=条件付きエントロピー(H(Y|X))が定義される。
  • 相互情報量I(X,Y)は,H(X) - H(X|Y)で定義され,Xの情報量(の期待値)からYを知っている時のXの情報量(の期待値)の差。絶対に0以上。(Yを知ることでは知れないXの情報量ってこと?)
  • 相対エントロピー(=カルバック・ライブラー情報量)は,2つの情報源x, yの確率分布の距離。

  • 確率変数が2つ以上の場合,結合エントロピーが定義される,つまり,結合エントロピーエントロピーの多変数版。

情報量

  • 事象Aが与えられた時に,その情報量って?というものに答える.
  • 確率を使って考える.発生が稀な事象と,よくある事象があった時にどっちに遭遇した場合が情報の量が増えたと思うか?ここで,稀な事象の方がゴミみたいな事象なんだから情報は無い,と考えるんじゃなくて,めったに遭遇できない事象に出会えたらそこで知れる情報の量は大きい(価値が高いって感じ?)と考える.
  • そこで,発生確率が小さいほど大きくなるような単調減少関数で情報量を定義しよう,という発想になる.
  • 次に,どんな単調減少関数がいいかな?と考える.そこで,独立性な事象における情報の加法性を考えてみる.事象AとB,それぞれの情報量I(A),I(B)があった時に,I(A∧B)は?と考えると,独立なんだから,合わさって情報量が増えることはないでしょう,単純に和でしょ?と考える.独立な確率の同時確率はP(A)*P(B).これを情報量化すると和になってくれればいい,,,,ってことでよくある-log(P)が定義される.
  • 事象A,その発生確率をP(A)とすると,情報量I(A)は以下で定義される.定義されているということが重要.あくまでも定義なんだ
I(A) = -log(P(A))    (1)

エントロピー

  • ある情報源(事象発生源)Xが与えられた時に,その情報源のランダムさは?(逆に言うとランダムでなくて推定できる部分は?)という質問に答える.
  • 情報量の期待値をエントロピーHと定義する.
  • よって,離散系なら下記で計算される.
H(x) = sum{-logP(x) * P(x)}   (2)
  • エントロピーはP(x)が一様分布の時に最大になる.一様分布な時,つまり完全ランダムな時に最大であるということ.

相互情報量

  • 2つの情報源X,Yがあった時に,その情報源の互いの情報量は?という疑問に答える.つまり,一方の情報源Xの情報を知った時に,Yの情報をどの程度わかるかな?という問い.
  • 確率の独立性と関係があるのが直感的にもわかる.
  • 計算方法は下記.ここで,H(X,Y)はXとYの同時事象の情報量の期待値(結合エントロピーと言う.XとYの両方から知れること).式から想像できるように,それぞれの独立の時のエントロピーから,重複部分を除く,というよくあるド・モルガンの図のイメージ.
I(X,Y) = H(X) + H(Y) - H(X,Y)   (3)

それぞれのエントロピーの関係

  • ド・モルガン図でイメージすると分かりやすい.
  • 事象Xのエントロピーと事象Yのエントロピーがそれぞれあるとする.
  • 相互情報量は重なっているA&Bの部分.ここが,Xを知ってYを知れることだし,Yを知ってXを知れることだもんね.
  • 結合エントロピーはA|Bなエントロピー.つまり,重複も許して,XとYから知れること.
  • 紛らわしいのが条件付きエントロピー.意味は,Xを知っている状況でYから知れること.これは包含図でいうと,H(X)-H(X&Y)の部分.つまり,重複部分を除いた,純粋にXだけからしか知れないこと.
  • もう一つ忘れてならないのがKLダイバージェンス.2つの分布の距離を測るものらしい,が式から直感的に理解ができない.2つの分布の比の一方からみた期待値,という感じなのかな?
  • KLDとMIの関係で悩むけれど,よく分からない・・・.MIは2つの確率変数X,Yに対する定義でKLDは1つの確率変数上の2つの分布で書かれているのを目にするけど,KLDはそれに限定されているのかな?もっと勉強が必要だ.