研究会

機械学習、データベース、分散システム、その他技術的なことを書く研究会です

パーセプトロンで多クラスの分類

はじめに

shop.ohmsha.co.jp

わかりやすいパターン認識の第 2 章で多クラスを分類するパーセプトロンが説明されていたので実装してみた。

学習データは パーセプトロンで Iris データセットの 2 クラス分類 - 研究会 の時のように Iris データセットを使おうと思ったが、どの特徴量を選んでも線形分離できないっぽかったので疑似乱数で生成したパターンを用いた。

コード

コードは以下。重みはクラス数×パターン数の行列として表現している。

from matplotlib import pyplot as plt
import numpy as np


class MultiClassPerceptron:
    def __init__(self, x_dim, n_class, rho=1e-3):
        self.W = np.random.randn(n_class, x_dim + 1)
        self.rho = rho

    def train(self, data, label):
        while True:
            # shuffle
            perm = np.random.permutation(len(data))
            data, label = data[perm], label[perm]

            classified = True

            for x, y in zip(list(data), list(label)):
                pred = self.predict(x)
                if pred != y:
                    classified = False

                    # update weight
                    x = np.array(list(x) + [1])
                    self.W[y] = self.W[y] + self.rho * x
                    self.W[pred] = self.W[pred] - self.rho * x

            if classified:
                break

    def predict(self, x):
        x = np.array(list(x) + [1])
        return np.argmax(np.dot(self.W, x), axis=0)


if __name__ == '__main__':
    x_c0 = np.random.randn(50, 2)
    x_c1 = np.random.randn(50, 2) + np.array([0, 8])
    x_c2 = np.random.randn(50, 2) + np.array([8, 0])

    perceptron = MultiClassPerceptron(2, 3)

    perceptron.train(np.concatenate([x_c0, x_c1, x_c2]),
                     np.array([0] * 50 + [1] * 50 + [2] * 50))

    # display
    plt.scatter(x_c0[:, 0], x_c0[:, 1], label='c0')
    plt.scatter(x_c1[:, 0], x_c1[:, 1], label='c1')
    plt.scatter(x_c2[:, 0], x_c2[:, 1], label='c2')

    W = perceptron.W
    w_0_1 = W[0] - W[1]
    w_1_2 = W[1] - W[2]
    w_2_0 = W[2] - W[0]
    y_0_1 = lambda x: w_0_1[0] / -w_0_1[1] * x + w_0_1[2] / -w_0_1[1]
    y_1_2 = lambda x: w_1_2[0] / -w_1_2[1] * x + w_1_2[2] / -w_1_2[1]
    y_2_0 = lambda x: w_2_0[0] / -w_2_0[1] * x + w_2_0[2] / -w_2_0[1]
    x = np.arange(-2, 10, 0.1)
    plt.plot(x, y_0_1(x), label='c0 - c1')
    plt.plot(x, y_1_2(x), label='c1 - c2')
    plt.plot(x, y_2_0(x), label='c2 - c0')

    plt.xlabel('x')
    plt.ylabel('y')
    plt.legend()
    plt.show()

結果

実行結果はこんな感じ。

f:id:ntsujio:20180215030031p:plain

決定境界を引いてみたけどかえって分かりにくいかもしれない (領域で色分けしようと思ったけど力尽きた・・・)。けどうまく分類できそうな境界になってる気がする。

あとこの実装だと決定境界が常に 1 点で交わるのだけど、たぶんこれはわかりやすいパターン認識の第 4 章 (61 ページ) で説明されている、「(c) 識別関数 {g_i(x)} の大小によってクラスを決定できる場合」に該当するので、リジェクト領域がないということなのだろう。

まとめ

  • 多クラス分類できるパーセプトロンを書いた
  • 領域を可視化するの難しい
  • 出力層の値が最大になるクラスという基準で分類すると決定境界が 1 点で交わった

参考

パーセプトロンで Iris データセットの 2 クラス分類

はじめに

shop.ohmsha.co.jp

わかりやすいパターン認識の第 2 章でパーセプトロンが説明されていたので実装してみた。

学習データは scikit-learn が提供している Iris データセットを使った。

Iris (アヤメ) データセットは Setosa, Versicolour, Virginica の 3 品種 (クラス) のパターンがまとめられていて、特徴量としてがくの長さ、幅、同じく花弁の長さ、幅の 4 次元が選択されている。

試しに花弁の長さと幅を軸にとってデータを散布図に起こしてみた。

f:id:ntsujio:20180214011431p:plain

そしてそれぞれの品種の画像が以下。

Iris setosa - Wikipedia

f:id:ntsujio:20180214011224j:plain

Iris versicolor - Wikipedia

f:id:ntsujio:20180214010903j:plain

Iris virginica - Wikipedia

f:id:ntsujio:20180214010952j:plain

確かに Setosa, Versicolour, Virginica の順に花弁が大きい・・・気がする。

コード

パーセプトロンは線形分離可能なクラスしか分類できないらしいので、花弁の大きさで線形分離できそうな Setosa と Versicolour を分類してみた。

import os
from matplotlib import pyplot as plt
from matplotlib.font_manager import FontProperties
import numpy as np
from sklearn import datasets, model_selection


class Perceptron:
    def __init__(self, x_dim, rho=1e-3):
        self.w = np.random.randn(x_dim + 1)
        self.rho = rho

    def train(self, data, label):
        while True:
            # shuffle
            perm = np.random.permutation(len(data))
            data, label = data[perm], label[perm]

            classified = True

            for x, y in zip(list(data), list(label)):
                pred = self.predict(x)
                if pred != y:
                    classified = False

                    # update weight
                    x = np.array(list(x) + [1])
                    self.w = self.w - pred * self.rho * x

            if classified:
                break

    def predict(self, x):
        x = np.array(list(x) + [1])
        return 1 if np.dot(self.w, x) > 0 else -1


if __name__ == '__main__':
    dataset = datasets.load_iris()

    x_train, y_train = dataset.data, dataset.target

    perceptron = Perceptron(x_dim=2)

    # preprocess train data
    mask = np.bitwise_or(y_train == 0, y_train == 1)
    x_train = x_train[mask][:, 2:]
    y_train = y_train[mask]
    y_train = np.array([-1 if y == 0 else 1 for y in y_train])

    # train
    perceptron.train(x_train, y_train)

    # display
    fp = FontProperties(fname=r'C:\Windows\Fonts\meiryo.ttc', size=12)

    x_c0 = x_train[y_train == -1]
    x_c1 = x_train[y_train == 1]
    plt.scatter(x_c0[:, 0], x_c0[:, 1], label='Setosa')
    plt.scatter(x_c1[:, 0], x_c1[:, 1], label='Versicolour')

    w = perceptron.w
    y = lambda x: w[0] / -w[1] * x + w[2] / -w[1]
    x = np.arange(1, 5, 0.1)
    plt.plot(x, y(x))

    plt.xlabel('花弁の長さ', fontproperties=fp)
    plt.ylabel('花弁の幅', fontproperties=fp)
    plt.title('パーセプトロンによるアヤメ科の花の分類', fontproperties=fp)

    plt.legend()
    plt.show()

実行結果

こんな感じな図が描けた。ちゃんと分類できそうな境界が引けている。

f:id:ntsujio:20180214012353p:plain

ちなみに、Versicolour と Virginica だと線形分離できないので学習時に無限ループしてしまった。

まとめ

  • パーセプトロンで Iris データセットを分類してみた
  • Setosa と Versicolour は花弁の大きさで決定境界を引けた
  • Versicolour と Virginica は花弁の大きさでは決定境界を引けなかった

参考

Nearest Neighbor 法で MNIST データセットの分類

はじめに

shop.ohmsha.co.jp

わかりやすいパターン認識の第 1 章で k-NN 法が説明されていたので、MNIST データセットを分類してみる。

コード

コードは以下の通り。特徴ベクトルとしてはピクセルごとの濃淡値をそのまま使い、距離尺度としてはユークリッド距離を用いた。

import os
from matplotlib import pyplot as plt
import numpy as np
from sklearn import datasets, model_selection

K = int(os.environ.get('NN_K', 5))
CLUSTER_SIZE = int(os.environ.get('NN_CLUSTER_SIZE', 10))


def distance(x, y):
    return np.sqrt(np.sum((x - y)**2))


def predict(x, prototypes):
    # calculate distances from x for each prototype
    distances = []
    for cls, data in prototypes.items():
        distances.extend([(int(cls), distance(x, y)) for y in data])

    distances.sort(key=lambda d: d[1])

    # count classes of top K
    class_count = [0] * 10
    for cls, _ in distances[:K]:
        class_count[cls] += 1

    # return the class having maximum class count
    return max(enumerate(class_count), key=lambda i: i[1])[0]


if __name__ == '__main__':
    mnist = datasets.fetch_mldata('MNIST original', data_home='.')

    x_train, x_test, y_train, y_test = model_selection.train_test_split(
        mnist.data, mnist.target, test_size=0.5, shuffle=True
    )

    # choose prototypes for each class
    prototypes = {}
    for i in range(10):
        data = x_train[y_train == i]
        prototypes[str(i)] = data[:CLUSTER_SIZE]

    # predict
    predicts = np.array([predict(x, prototypes) for x in x_test])
    accuracy = len(x_test[predicts == y_test]) / len(x_test)

    print(f"accuracy = {accuracy}")

結果

CLUSTER_SIZE を変化させて分類精度と処理時間をグラフにしてみた。

K は CLUSTER_SIZE = 1 のときは 1、それ以外の時は 10 に設定した。

f:id:ntsujio:20180213001728p:plain

CLUSTER_SIZE が 1000 のときは分類精度が 0.95 と、単純方法の割に予想以上に高い結果となった。

また処理時間は CLUSTER_SIZE に線形になった。最近傍を求めるためにクラスター内の全パターンとの距離を計算しているためであろう。

まとめ

  • k-NN で MNIST を分類してみた
  • 単純な方法でも以外と精度出た
  • データ量大事

参考

MNIST データセットの読み込み方

機械学習を学習する上で学習データを準備するのに苦労する場面がよくある。

今回は MNIST データセットと呼ばれる、機械学習ベンチマークでよく使われるデータセットの使い方をまとめる。

MNIST データセット

MNIST データセット は手書き数字文字データをまとめたデータセットであり、訓練用に 60,000 枚、テスト用に 10,000 枚の画像データ、そしてそれぞれの正解データ (画像がどの数字を表しているか) が用意されている。

データの形式は独自のバイナリで、ページの下のほうにフォーマットが解説されている。プログラムで扱う際はこのバイナリをパースして使う。

コード

ページの解説に従ってバイナリをパースするコードが以下。Python でバイナリをパースするのは struct モジュールを使った。

gist7b55be06fba08e00a51edf9e4f2eb207

読み込んだ画像の表示

画像は matplotlib モジュールで表示できる。表示コードと結果は以下の通り。

gistf9ec5c6229fa65402b8bbe012d23c887

f:id:ntsujio:20180211205121p:plain

画像は「5」のデータ。ちゃんと読み込めてるっぽい。

scikit-learn で読み込む

実は MNIST データセットは自分でコードを書かなくてもバイナリを読み込める。

機械学習ライブラリである scikit-learn を使うとこんな感じに書ける。

from sklearn import datasets

mnist = datasets.fetch_mldata('MNIST original', data_home='.')

img = mnist.data[0].reshape(28, 28)

plt.imshow(img, cmap='gray')
plt.show()

fetch_mldata メソッドのドキュメントを見ると、データは mldata.org から取得すると書いてある。

mldata.org は MNIST データセットだけでなく、様々なデータセットを公開している。また有用なデータセットをアップロードして貢献することもできる。

MNIST (Original) のページを見ると CC0 ライセンスと記載されており、学習にとても有用である。感謝。

まとめ

  • 機械学習の学習データには MNIST データセットが便利
  • MNIST データセットの読み込みには scikit-learn が便利
  • 便利なものを用意してくれている先人に感謝

参考

人工知能を初めから学びなおそうと思った

最近、二十代も終盤に差し掛かり、技術者として何か一本これというスキルを得たいと思い始めている。

世間では AI、コンテナ、ブロックチェーン、ドローン…等々、華やかな話題で賑わっているが、さて自分はどのような分野に興味があるだろうか。

これまでの人生を振り返ってみると、自分は絵だったりプログラムだったり、何か「作品」を作ることが好きだったように思える。

それでいくと、AI の分野ではゴッホに絵を描かせたり (ゴッホの絵をコンピュータに描かせるとどうなるか? - GIGAZINE)、白黒画に自動着色したり (「あなたの絵をAIが自動着色します!」 コミケで体験 - ITmedia NEWS) といった作品が発表されている。また機械学習で動く木 (How to Walk Branches, 前川和純 他, 第19回東京大学制作展 “WYSIWYG?”) みたいな、原始の知能を感じさせる作品も感動する。これを自分もやってみたい。

しかしこれまで人工知能については熱心に勉強してこず基礎が分かっていない感があるので、いきなりディープラーニングとか強化学習に手を出しても何もできないまま終わる可能性がある。

とりあえず学部生レベルから復習するために、確率統計の基礎から学びなおすことにした。

ということでこれから復習系の投稿が増える予定。続くといいな。

Freenet のプラグインを作ろう: HelloWorld プラグインの作成

はじめに

最近 Freenetプラグインを書く必要に迫られ情報収集をしている。しかし開発に当たって必要な情報が英語でも日本語でも纏まっていない印象なのでここに記しておく。

Freenet はインターネット検閲に対抗するため匿名でのコミュニケーションを実現する P2P 型システムである。ユーザーからは分散型のストレージのような感じで使える。そしてそのストレージ機能を利用して匿名メールや匿名掲示板のようなものを実現するプラグインが開発されている。

今回は公式 Wikiチュートリアル に則って簡単な HelloWorld プラグインを作成する。

ソースコード

HelloWorld.java

/**
 * 参考: https://wiki.freenetproject.org/Plugin_API
 */

import freenet.pluginmanager.*;
import java.util.Date;

public class HelloWorld implements FredPlugin {
    private volatile boolean goon = true;

    /* Freenet の API にアクセスするためのオブジェクト */
    PluginRespirator pr;

    /* プラグインがアンロードされた時に呼ばれる */
    public void terminate() {
        goon = false;
    }

    /* プラグインがロードされた時に呼ばれる */
    public void runPlugin(PluginRespirator pr) {
        this.pr = pr;

        while (goon) {
            /* 標準エラーへの出力は wrapper.log ファイルに吐かれる */
            System.err.println("Heartbeat from HelloWorld-plugin: " + (new Date()));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // Who cares ?
            }
        }
    }
}

manifest.mf

※ jar を作るときに生成されるマニフェストファイルへ追記する設定。Freenetプラグインのエントリーポイントを知らせる。

Plugin-Main-Class: HelloWorld

ビルド

Freenet のインストールフォルダー (Windows でのデフォルトは AppData\Local\Freenet) に freenet.jar があるはずなので、クラスパスとして指定しつつビルドする。また jar 作成時にマニフェストファイルも指定する。

javac -cp path\to\freenet.jar HelloWorld.java
jar -cvmf manifest.mf HelloWorld.jar HelloWorld.class

実行

Freenet の Web インターフェースにアクセス (localhost:8888 あたり) し、上部の「設定」タブから「プラグイン」に移動、下部の「非公式プラグインを追加」で作成した HelloWorld.jar を指定してロードする。

AppData\Local\Freenet\wrapper 付近にある wrapper.log というファイルを見ると以下のような出力が確認されるはず。

INFO   | jvm 1    | 2014/12/14 02:31:56 | Downloading plugin path\to\HelloWorld.jar
INFO   | jvm 1    | 2014/12/14 02:31:56 | Heartbeat from HelloWorld-plugin: Sun Dec 14 02:31:56 JST 2014
INFO   | jvm 1    | 2014/12/14 02:31:57 | Heartbeat from HelloWorld-plugin: Sun Dec 14 02:31:57 JST 2014

プラグインのアンロードは「Plugins currently loaded」から HelloWorld プラグインを見つけて「アンロード」ボタンを押す。

まとめ

WebRTC を使った P2P ファイル共有ソフトは違法か

概要

WebRTC を使った P2P ファイル共有アプリケーションを公開することは果たして著作権的に違法なのかどうかというお話です。

※ 私は法律全般に関して素人です。本記事はそれを踏まえてお読みください。

詳細

WebRTC によってお手軽に Web ブラウザー間で P2P 通信を行えるようになりました。P2P 型のソフトウェアといえばファイル共有ソフトが代表的ですが、このファイル共有ソフトはこれまで著作権を侵害するようなファイルの交換に多く利用されてきたという歴史があります。

国内外を問わずこれまで P2P ファイル共有ソフトを作成した人に対する著作権侵害の訴訟は多く行われてきました。そこで今回はそれらの事例を踏まえながら、昨今登場した WebRTC を使った P2P ファイル共有ソフトを作って公開することは果たして違法なのかどうかを検討したいと思います。

事例 1: ナップスター事件

ナップスターP2P ファイル共有ソフトとして最も初期のものです。ナップスターも例に漏れず多くの違法ファイルの交換に利用され、1999 年にレコード会社がナップスター著作権侵害カリフォルニア地裁に提訴し、最終的に 2001 年の控訴審ナップスター側が敗訴しています。

ナップスター敗訴の理由としてそのアーキテクチャーが関わっています。ナップスターはハイブリッド P2P と呼ばれ、中央サーバーにそれぞれのユーザーが公開しているファイルのファイル名がインデックスされています。ユーザーは中央サーバーに「自分が欲しいファイルを誰が持っているか」を問い合わせ、その応答をもとにファイルを持っている人にダウンロードのアプローチをかけます。

このアーキテクチャーの場合、中央サーバーには「どのようなファイルが流通しているか」の情報が集約されます。つまり違法ファイルが大量に流通している場合、中央サーバーを運用している人はそのことを把握しているはずだと見なされます。そしてそれを知っていながらサーバーの運用を続けた場合、著作権侵害の幇助になり得るようです。

※ ただしこの判決は米国でのものだという点に注意

事例 2: グロックスター事件

これに対してグロックスターはピュア P2P と呼ばれるアーキテクチャーを取るファイル共有ソフトです。ピュア P2P はハイブリッド P2P とは違いファイル名をインデックスする中央サーバーを持たず、その役割は P2P ネットワークに参加しているユーザーが担います。

このアーキテクチャーの場合グロックスターを配布している人はどのようなファイルがネットワークに流通しているかについて管理できません。よって著作権侵害の幇助を問いづらそうです。事実、グロックスターも米国で裁判が行われましたが、一審二審はグロックスター側が勝訴しました。

しかし最高裁では判決が逆転し、グロックスター側が敗訴しました。その理由として、グロックスターを配布するときの態度が問題だったということです。グロックスターの配布者は「グロックスターを使えば有名な音楽にアクセスできる」と宣伝したり、ユーザーからの著作物の探し方などの質問に答えていたりしたそうです。このような態度により「そそのかし」の責任を問われ、敗訴のひとつの原因になったようです。

※ これも米国の事例

事例 3: ファイルローグ事件

日本の例を見てみましょう。ファイルローグはナップスターと同様に中央サーバーを立てるハイブリッド P2P 型のソフトウェアであり、MMO 社によって開発されました。

日本ではカラオケ法理という判例著作権侵害裁判でよく参照されるそうですが、ファイルローグ事件もカラオケ法理に照らし合わせた結果、違法という判決になりました。

理由として、やはり中央サーバーを立ててファイル名を管理していること、そしてあまりにも違法ファイルの流通の割合が高かったことが挙げられています。

事例 4: Winny 事件

そして有名な Winny 事件です。Winnyグロックスターと同じく中央サーバーを立てないピュア P2P 型のソフトウェアです。

Winny は 2004 年の京都地裁に始まり最高裁まで争いましたが、2011 年に Winny 側の逆転勝訴で幕を閉じました。

Winny 側勝訴の理由として、配布時に著作権侵害の用途に利用しないことを再三注意していたことが挙げられています。これにより作者に著作権侵害幇助の意図がなかったと判断されたようです。

では WebRTC はどうか

これまでの事例をまとめると、 作者に著作権侵害の意図がないことを前提として

  1. 中央サーバーを持たない ピュア P2P 型であること
  2. 違法行為に用いないことを注意し続けること

が重要なポイントであることが分かります。

さて WebRTC では一般的に相手と通信を始めるためにシグナリングサーバーが必要になります。シグナリングサーバーは「誰がネットワークに参加しているか」を管理していると言えます。

普通は「誰が参加しているか」だけでその人が違法ファイルをやり取りしているかは判断できないので、一般的な感覚ではそれを理由に違法だと判断されることはないように思います。

まとめ

作成した P2P ファイル共有ソフト著作権侵害に問われないようにするためには

  • 中央サーバーを持たない ピュア P2P 型であること
  • 違法行為に用いないことを注意し続けること

そして何より

が重要である

参考