2016年1月20日水曜日

MNISTの自己組織化マップ(SOM)を作った[Python]

さっき昔の記事の修正してたら操作を誤ってしまい,古い投稿がさっき投稿されたようになっててちょっとつらいです…….なのでもう少し温めてから出そうと思ってた内容をさっさと書いちゃいます.

自己組織化マップというのは,教師なしクラスタリングの一種で,似た者同士がまとまっていくものです.詳細はプログラムのコメントに書いたリンク先を参照してください.

とりあえずこういった感じの画像が得られます.(実際に使うときはこういった画像を探してくるのではなくて写像した2次元座標を使います.scikit-learnにもクラスが無く,ひょっとしたら可視化以外に使う人はもういないのかもしれません)

MNISTの手書き数字を似た者同士でまとめてる感じです.上のような画像を作るには結構演算が必要なので以下のプログラムのパラメータを幾つか変更してやる必要がありますが,基本的にはこのプログラムで出来ます(n_side=50,n_learn=5000ぐらい).これを見て,画像認識のプログラムを作る際に,どういった特徴量を作るかを考えるのに役立てることができます.

まぁ画像以外にも元素のデータ突っ込んで似た元素でまとめたりとか,動物の特徴から似た者同士をまとめたりとかという使い方もあります.よくネットに上がっている例だと色の自己組織化マップがあります.そんなに時間がかからないので出来る様子を動画にしてみました.

最初は結構変化しますが,時間が立つに連れて落ち着いてくるようすがわかります.

以下ソースコード

PythonでWebカメラの画像を保存する[Python]

 Pythonで画像処理の勉強をちょっとやってみたいと思い,とりあえずWebカメラから画像を取得してみた。

1.OpenCVのインストール


 OpenCVの公式サイトのダウンロードページからOSに合ったバージョンをダウンロードしてインストールする。opencvフォルダ内のbuild\python\2.7\x86\cv2.pydをPython27フォルダ内のLib\site-packagesにコピーすれば大丈夫なはず。

2.プログラム

 俺が書いたみたいに見せてるけれどOpenCVの公式ドキュメントをちょこっと変えただけでなんか申し訳ない。sを押すとphoto.jpgという名前で保存される。qを押すと終了する。

# -*- coding: utf-8 -*-
import numpy as np
import cv2

cap = cv2.VideoCapture(0)

while(True):
    # フレームをキャプチャする
    ret, frame = cap.read()

    # 画面に表示する
    cv2.imshow('frame',frame)

    # キーボード入力待ち
    key = cv2.waitKey(1) & 0xFF

    # qが押された場合は終了する
    if key == ord('q'):
        break
    # sが押された場合は保存する
    if key == ord('s'):
        path = "photo.jpg"
        cv2.imwrite(path,frame)

# キャプチャの後始末と,ウィンドウをすべて消す
cap.release()
cv2.destroyAllWindows()


 左はArduinoの箱。特に意味は無いです。右は画面を撮影したらこうなった。写し鏡みたいな。こちらも特に意味は無いです。

 ちなみにcv2.Cannyという関数を使うとエッジ検出ができます。ラズパイでは難しいかもしれないですが,普通のパソコンならリアルタイムで処理できるはずです。

# -*- coding: utf-8 -*-
import numpy as np
import cv2

cap = cv2.VideoCapture(0)

while(True):
 # フレームをキャプチャする
 ret, frame = cap.read()

 # エッジ検出
 frame = cv2.Canny(frame,100,200)

 # 画面に表示する
 cv2.imshow('frame',frame)

 # キーボード入力待ち
 key = cv2.waitKey(1) & 0xFF

 # qが押された場合は終了する
 if key == ord('q'):
  break
 # sが押された場合は保存する
 if key == ord('s'):
  path = "photo.jpg"
  cv2.imwrite(path,frame)

# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()


 こちらエッジがされた画像。緑茶の二文字が見て取れる。画像処理と言っても既存の関数を使うだけで中身を知らなくてもできるという現実が嬉しいような悲しいような……。

画像を油絵風に変換する[C#]

 機能はメインのPCのOS(Windows10)が起動しなくなった。まぁ調べたところOSが悪いというよりもう寿命だったみたい。CPUが熱で落ちて運悪く重要なファイルが破損したようです。

 今は再インストールをしている最中です(´Д`)ハァ…。

 愚痴はこのへんにして,今日は画像を油絵風に変換するプログラムを作成しました。

1.アルゴリズム


 比較的簡単な方法です。

 まず元の画像からランダムに一個点を選びます。


 次にその点の色を取り出します。ここでは赤とします。次にその点の周辺にその色の円を書きます。この時ちょっと透明度を入れるといいです。


 まぁこんな感じ。今手作業で適当にやったから辺だけど,本当はもう少しいい感じになる。これを繰り返すとこんな感じになる。

 半透明の円が組み合わさって元の色を作る。他の色との境界部分は混ざり合うという算段です。これが本当に油絵風と言っていいのかは読者様の感性にお任せします。

2.ソースコード

ソースコードにするとこんな感じ。
/**
 *  2015/10/19
 *  画像を油絵風に変換するプログラム
 */

using System;
using System.Drawing;

class Program
{
 // 一辺をどのぐらい分割するか
 // この値を大きくすると精細な画像になる
 const int N = 128;
 
 static void Main()
 {
  Console.WriteLine("a.jpgという画像をを読み込みます");
  
  var img = new Bitmap("a.jpg");
  
  Console.WriteLine("画像を変換し,b.jpgという名前で保存します");
  
  OilPainting(img).Save("b.jpg");
  
  Console.WriteLine("保存が完了しました");
 }
 
 // 油絵っぽい画像に変換するプログラム
 static Bitmap OilPainting(Bitmap src)
 {
  // 描画用のBitmapクラス
  var img = new Bitmap(src);
  // 長辺の長さを出す
  var min = Math.Min(img.Width,img.Height);
  // 円の半径を計算する
  var r = min / N;
  // 乱数生成用クラス
  var rand = new Random();
  // 打点回数(絵全体を覆える回数にしなければならない)
  var count = 6*img.Width * img.Height / (r*r);
  // 描画するのに必要なGraphicsクラス
  var g = Graphics.FromImage(img);
  
  for(int i = 0 ; i < count ; i++ ) 
  {
   // ランダムに点を決める
   var x = rand.Next(src.Width);
   var y = rand.Next(src.Height);
   // 色を取り出す。この時透明度を入れておくといい感じになる
   var color = Color.FromArgb(128,src.GetPixel(x,y));
   // 描画に必要なSolidBrushクラスを先ほど作成した色からつくる
   var brush = new SolidBrush(color);
   
   // 円の描画
   // 円の中心が(x,y)に来るように調整してある
   g.FillEllipse(brush,x-r,y-r,2*r,2*r);
  }
  
  // 変換後の画像を返す
  return img;
 }
}

 「ぱくたそ」さんから借りてきた画像を変換してみた。


 もう一度言いますがこれが本当に油絵風と言っていいのかは読者様の感性にお任せします。

BigIntegerを使って累乗和の公式を計算[C#]

 累乗和の公式シリーズ完全版です。べき乗和の公式の計算結果はこちらです。見づらいし利用価値もないけれど,とりあえずネットにあげとくと頭のいい人は面白い使い道思いついたりするからね。

1.BigInteger構造体とは

たぶんべき乗和の公式よりもこっちのほうが需要ありそうなので説明します。べき乗和の方はアルゴリズムを以前説明したのでそちらを参照してください。

 BigInteger構造体はでっかい数字を扱うためのint型。int型だから整数しか扱えない。でもメモリが使えるだけ桁数を増やせる。オーバーフローしない。すごい。そんなクラスです。

 ただいくつか設定をしないと使えません。コマンドプロンプトでコンパイルをしているという方は「csc -r:System.Numerics.dll *.cs」というように参照を追加します。

 VisualStudioでは以下の様な参照の追加を行います。ソリューションエクスプローラーの参照から,参照の追加をクリック。
アセンブリ→フレームワーク→System.Numerics.dllにチェックを入れる。
これで準備が完了。次のプログラムを打ち込んでみる。
// コンパイル時にはSystem.Numerics.dllを参照追加すること

using System;
// BigIntegerを使うために必要
using System.Numerics;

class Program
{
 static void Main()
 {
  // お行儀のよい宣言
  BigInteger a = new BigInteger(3);
  // int型からは暗黙の型変換が可能
  BigInteger b = 5;
  
  // 掛け算,割り算も普通のint型のように使える
  b = 1024 * b;
  
  // いくつかの数学関数はBigInteger構造体に用意されてる
  a = BigInteger.Pow( a , 20 );
  
  // 出力も簡単
  Console.WriteLine("{0}",a);
  Console.WriteLine("{0}",b);
 }
}

 まぁ何も気にせずに使えるということがよくわかりましたね。詳しい情報はやっぱり公式のMSDNさんが一番だと思います。

2.プログラム

べき乗和の公式を求めるプログラムです。一応任意のべき乗数を求めることができるようになりました。100乗和の公式を求めてやるぜ!と冗談半分で言っていたのですが,何とかうまくいきました。

 例えば20乗和の公式は
となることがわかります。ただ残念ながら出力結果はこんな画像にはなりません(´・ω・`)。

 ちなみに今回255乗和の公式までは求めました(→計算結果)。結構時間がかかりましたが数十分程度なのでまだまだ上は目指せそうですよ。

 たいていMathematicaみたいなお高いソフト使って,誰かがこういうのは求めているものですが,1000乗和の公式とか計算したら,この世で最初に自分が計算した式かもしれない……と思うとワクワクしますね。

using System;
using System.Numerics;

class Program
{
    static void Main()
    {
        Console.WriteLine("何乗和の公式を求めますか?");
        var N = int.Parse(Console.ReadLine()) + 1;

        // 係数を求める連立方程式の拡大係数行列を作る
        var formula = MakeFormula(N);
        // 連立方程式をガウス消去法で計算
        Solve(formula, N);
        // 結果を表示
        Show(formula, N);

        Console.WriteLine("値を代入します");
        while (true)
        {
            Console.Write(" n = ");
            var n = int.Parse(Console.ReadLine());
            var f = new Fraction(0);

            for (int i = 0; i < N; i++ )
                f += BigInteger.Pow(n, i+1) * formula[i, N];
            Console.WriteLine(f.ToString());
        }
    }

    // 連立方程式を解くメソッド
    static void Solve(Fraction[, ] formula, int N)
    {
        Fraction temp;

        // ガウス消去法

        // 前進消去
        for (int i = 0; i < N; i++)
        {
            // まれに対角成分が0の時がある
            if (formula[i, i] == 0)
            {
                // そういった場合は下の行から探してくる
                for (int j = i + 1; j < N; j++)
                {
                    // i行目が0でない行が見つかった場合
                    if (formula[j, i].ToDouble() != 0.0)
                    {
                        for (int k = i; k < N + 1; k++)
                        {
                            // 下の行から0でない値を持ってくる
                            formula[i, k] += formula[j, k];
                        }
                        break;
                    }
                }
            }

            // 対角対角成分をを1にする
            temp = formula[i, i];

            for (int j = 0; j < N + 1; j++)
                formula[i, j] /= temp;

            // i+1行以降のi列目を0にする
            for (int j = i + 1; j < N; j++)
            {
                temp = formula[j, i];
                for (int k = 0; k < N + 1; k++)
                    formula[j, k] -= temp * formula[i, k];
            }
        }

        // 後進消去
        for (int i = N - 1; i >= 0; i--)
        {
            // 対角成分以外を0にする
            for (int j = i - 1; j >= 0; j--)
            {
                formula[j, N] -= formula[j, i] * formula[i, N];
                formula[j, i] = 0;
            }
        }
    }

    // 累乗和の公式を表示するメソッド
    static void Show(Fraction[, ] d, int N)
    {
        Console.WriteLine("{0}乗和の公式は……", N - 1);
        for (int i = 0; i < N; i++)
        {
            if ( d[i, N] != 0 )
            {
                if ( i != 0 )
                    Console.Write("+");

                Console.Write("({0})*n^{1}", d[i, N], i + 1);
            }
        }
        Console.WriteLine();
    }

    // 累乗和の公式を求める連立方程式の拡大係数行列を作るメソッド
    static Fraction[, ] MakeFormula(int N)
    {
        var ret = new Fraction[N, N + 1];
        var sum = new BigInteger(0);

        for (int i = 0; i < N; i++)
        {
            // 係数部分を作る
            for (int j = 0; j < N; j++)
                ret[i, j] = BigInteger.Pow(i + 1, j + 1);

            // 答えの部分を作る
            sum = sum + BigInteger.Pow( i + 1, N - 1);
            ret[i, N] = sum;
        }

        return ret;
    }
}
class Fraction
{
    /// <summary>
    /// 分子
    /// </summary>
    public BigInteger Upper
    {
        get { 
            return _Upper;
        }
        set { 
            this._Upper = value; 
            CheckFraction();
        }
    }
    /// <summary>
    /// 分母
    /// </summary>
    public BigInteger Lower
    {
        get { 
            return _Lower;
        }
        set { 
            this._Lower = value; 
            CheckFraction();
        }
    }
    /// <summary>
    /// 分子の値を格納する変数
    /// </summary>
    private BigInteger _Upper;
    /// <summary>
    /// 分母の値を格納する変数
    /// </summary>
    private BigInteger _Lower;

    public Fraction(BigInteger Upper, BigInteger Lower)
    {
        Assignment(Upper, Lower);
    }
    public Fraction(BigInteger n)
    {
        Assignment(n, 1);
    }
    public Fraction(int n)
    {
        Assignment(n, 1);
    }
    public Fraction(Fraction f)
    {
        Assignment(f.Upper, f.Lower);
    }

    /// <summary>
    /// double型に変換するメソッド
    /// </summary>
    /// <returns>double型に変換した値</returns>
    public double ToDouble()
    {
        return (double)Upper / (double)Lower;
    }

    /// <summary>
    /// float型に変換するメソッド
    /// </summary>
    /// <returns>float型に変換した値</returns>
    public float ToFloat()
    {
        return (float)Upper / (float)Lower;
    }

    /// <summary>
    /// 約分や分母が0などのミスが無いか確認するメソッド
    /// </summary>
    private void CheckFraction()
    {
        BigInteger u = _Upper;
        BigInteger l = _Lower;
        BigInteger temp;
        bool sign;


        if (l == 0)
        {
            // 分母が0なのでゼロ除算の例外をぶん投げる
            throw new DivideByZeroException();
        } else if (u == 0)
        {
            // 分母が0ならばこうする
            _Upper = 0;
            _Lower = 1;
        } else
        {
            // 分数の値が負ならsign = true となる
            sign = l * u < 0;
            // とりあえずl,uを正にする(余りの計算をするため)
            l = BigInteger.Abs(l);
            u = BigInteger.Abs(u);

            // 常にu <= lとなるようにする
            if (u > l)
            {
                temp = u;
                u = l;
                l = temp;
            }

            // ユークリッドの互除法
            while (l % u != 0)
            {
                temp = l % u;
                l = u;
                u = temp;
            }

            // 約分
            _Upper = BigInteger.Abs(_Upper) / u;
            _Lower = BigInteger.Abs(_Lower) / u;

            // 正負を直す
            if (sign)
                _Upper = -_Upper;
        }
    }

    /// <summary>
    /// クラス内で代入を行うメソッド(ただしCheckFractonから呼んではならない)
    /// </summary>
    /// <param name="Upper">分母</param>
    /// <param name="Lower">分子</param>
    private void Assignment(BigInteger Upper, BigInteger Lower)
    {
        this._Upper = Upper;
        this._Lower = Lower;
        CheckFraction();
    }

    public static Fraction operator +(Fraction left, Fraction right)
    {
        BigInteger l = left.Lower * right.Lower;
        BigInteger u = left.Upper * right.Lower + right.Upper * left.Lower;

        return new Fraction(u, l);
    }

    public static Fraction operator -(Fraction left, Fraction right)
    {
        BigInteger l = left.Lower * right.Lower;
        BigInteger u = left.Upper * right.Lower - right.Upper * left.Lower;

        return new Fraction(u, l);
    }

    public static Fraction operator *(Fraction left, Fraction right)
    {
        BigInteger l = left.Lower * right.Lower;
        BigInteger u = left.Upper * right.Upper;

        return new Fraction(u, l);
    }

    public static Fraction operator /(Fraction left, Fraction right)
    {
        // 逆数にする(0除算ならここで例外が発生する)
        right = new Fraction(right.Lower, right.Upper);
        return right * left;
    }

    public static Fraction operator /(Fraction f, int n)
    {
        return new Fraction(f.Upper, f.Lower * n);
    }

    public static Fraction operator *(Fraction f, int n)
    {
        return new Fraction(f.Upper * n, f.Lower);
    }

    public static implicit operator Fraction(BigInteger n)
    {
        return new Fraction(n);
    }

    public static implicit operator Fraction(int n)
    {
        return new Fraction(n);
    }

    public static bool operator <(Fraction left, Fraction right)
    {
        return left.ToDouble() < right.ToDouble();
    }

    public static bool operator >(Fraction left, Fraction right)
    {
        return left.ToDouble() > right.ToDouble();
    }

    public static bool operator <=(Fraction left, Fraction right)
    {
        return left.ToDouble() <= right.ToDouble();
    }

    public static bool operator >=(Fraction left, Fraction right)
    {
        return left.ToDouble() >= right.ToDouble();
    }

    public static implicit operator double (Fraction f)
    {
        return f.ToDouble();
    }

    public static implicit operator float (Fraction f)
    {
        return f.ToFloat();
    }

    public static bool operator ==(Fraction left, Fraction right)
    {
        // nullかどうかは分子があるかどうかで判断する
        if (left.Upper == null || right.Upper == null)
            return false;
        return left.Upper == right.Upper && left.Lower == right.Lower;
    }

    public static bool operator ==(Fraction left, int n)
    {
        Fraction right = new Fraction(n);

        // nullかどうかは分子があるかどうかで判断する
        if (left.Upper == null || right.Upper == null)
            return false;

        return left.Upper == right.Upper && left.Lower == right.Lower ;
    }

    public static bool operator !=(Fraction left, int n)
    {
        Fraction right = new Fraction(n);

        return !(left == right);
    }

    public static bool operator !=(Fraction left, Fraction right)
    {
        return !(left == right);
    }

    public override bool Equals(object obj)
    {
        if (obj == null || this.GetType() != obj.GetType())
            return false;

        return (this == (Fraction)obj);
    }

    public override int GetHashCode()
    {
        return (int)((this.Upper * this.Lower) % int.MaxValue);
    }

    public override string ToString()
    {
        if ( this.Upper == 0 )
            return String.Format("0");
        else if ( this.Lower == 1 )
            return String.Format("{0}", this.Upper);

        return String.Format("{0}/{1}", this.Upper, this.Lower);
    }
}