• 集合知プログラミング

    集合知プログラミング
    購入したままになっていた「集合知プログラミング 」を読み始めた。

    ナレッジを扱うシステムに関わっているので、何かしら応用できないかな、と考えたのだった。

    よくあるのが検索の際に「プロクシとプロキシ」「引越しと引っ越し」を同じとみなすような検索が出来ないか?ということ。
    ※他に「ossとオープンソース/opensource」「セマンティックwebとsemantic web/semanticweb」みたいな。

    単純に考えると辞書を用意して検索ワードを拡張して検索エンジンに食わせよう、となる。けど、どうせなら自動的になんとかできないだろうか。

    で、「集合知プログラミング

    ユーザの与えた語に対し、類似あるいは関連の強い語を推薦できないかと考えた。
    googleのように巨大なコーパスを扱える状況ならともかく、少ないリソース・計算力でどうにかそれらしい結果を出したい。

    近年の分類方法の1つといえばタグ。多数のタグが集まる場所といえばソーシャルブックマーク。

    あるURLで識別されるリソースに対し、不特定多数の人がタグをつける。
    よほどおふざけな人が集まらない限り、ついたタグはリソースにまつわる一定の関連があるだろう。さらにそこには表記ゆれもある。これらタグ同士の相関係数を事前に計算しておいて提示するという試み。

    僕ははてブを使っている。ちょうどいいことに特定URLをブックマークに関する情報をJSONで取得するAPIがある。
    とりあえず自分のブックマークを起点に、他の人がつけたタグの数を取得し計算した。
    URLの数は1300ちょい。タグの種類は約5500種類。

    うーん、それっぽい結果になっているものもあるけど・・・・
    ※負の相関が降順なのは(僕のコードの)仕様です。
    ※ケンドールの順位相関係数です。

    セマンティックweb
        0.866666666667: semantic web
        0.8: semanticweb
        0.6: メタデータ
        0.6: web
        -0.733333333333: rdf
        -0.142857142857: xml

    rails
        0.733333333333: ruby on rails
        0.642857142857: ror
        0.6: soft
        0.52380952381: develop
        0.444444444444: web開発
        0.428571428571: rubyonrails
        0.418181818182: tutorial
        -0.8: manual
        -0.6: tools
        -0.6: 資料
        -0.52380952381: reference
        -0.4: ソフトウェア開発
        -0.357142857143: web制作
        -0.333333333333: リファレンス

    oop
        0.6: モデリング
        0.6: agile
        0.575757575758: オブジェクト指向
        0.563636363636: oo
        0.466666666667: design pattern
        0.433333333333: programming
        0.4: devel
        -0.6: 未読
        -0.6: 設計
        -0.428571428571: デザインパターン
        -0.4: programing
        -0.333333333333: design
        -0.222222222222: システム開発
        -0.2: develop

    母集団が小さいからなのか。母集団が偏っているのか。

    考えてみれば、ソーシャルブックマークのタグに全て(といいたくなるくらいたくさん、の意)の語が登場するわけではなく、「自分がブックマークするときのタグの推薦に使える」ぐらいの結果か。
    母集団の中には「ソーシャルブックマークを便利に使う工夫のタグ」も存在するので、そもそも推薦に向かないものがある。
    ※「*お役立ち」とか「2-便利」とか「あとでよむ」みたいな

    表記ゆれのネタにしようと思ったら、「同じURLに対して同じ人がつけたタグ同士」は表記ゆれではなくて、何かしら別の軸の意味を持った言葉のはず。なので、こういうタグ同士でペナルティを加味したらもう少しマシになるかな。

    ま、それでもなんとなーく相関のありそうな語の固まりが取り出せたのは、ちょっと面白かった。
    サンプルをどーんと拡大してhadoopで分散処理、みたいなことをやってみたいな。


  • 複数の.pptを 一括して.ppsで保存する

    複数の.pptをスライドショー形式(.pps)にぽちぽち名前をつけて保存する、を一括して実行するマクロなjscript。

    • 指定されたpptをスライドショー(pps)で書き出す。
    • エクスプローラ上でpptまたはフォルダをjsにドロップするか、コマンドプロンプトから実行
    • フォルダを指定した場合、同フォルダ内(直下)の*.pptを処理対象にする。
    • ファイル名.ppt→ファイル名.pps、同名のppsが既に存在する場合、問答無用で上書き
    • 実行は自己責任で行ってください。いかなる結果にも責任を負いかねます。

    cscript save_as_pps.js /out:出力ディレクトリ {入力ppt|入力フォルダ}...
      - 出力先明示
    cscript save_as_pps.js {入力ppt|入力フォルダ}...
      - 元pptと同じフォルダに出力

    ちらっとgoogle先生に聞いてみたら、2003以前だと拡張子をppsにするだけでいいらしい。。。

    ■手元の環境
    Windows XP Pro SP3 + MS Office 2003 Professional SP3
    Windows Vista Ultimate SP1 x64 + MS Office 2007 Standard SP1
    ■save_as_pps.js
    /**
    * 指定されたpptをスライドショー(pps)で書き出す。
    * エクスプローラ上でpptまたはフォルダをjsにドロップするか、
    * コマンドプロンプトから実行
    *
    * cscript save_as_pps.js /out:出力ディレクトリ {入力ppt|入力フォルダ}...
    * - 出力先明示
    *
    * cscript save_as_pps.js {入力ppt|入力フォルダ}...
    * - 元pptと同じフォルダに出力
    *
    * フォルダを指定した場合、同フォルダ内(直下)の*.pptを処理対象にする。
    *
    * My env.
    * - Windows XP Pro SP3 + MS Office 2003 Professional SP3
    * - Windows Vista Ultimate SP1 x64 + MS Office 2007 Standard SP1
    */


    // PowerPoint
    var PpSaveAsFileType= {
    ppSaveAsPresentation: 1,
    ppSaveAsPowerPoint7: 2,
    ppSaveAsPowerPoint4: 3,
    ppSaveAsPowerPoint3: 4,
    ppSaveAsTemplate: 5,
    ppSaveAsRTF: 6,
    ppSaveAsShow: 7,
    ppSaveAsAddIn: 8,
    ppSaveAsPowerPoint4FarEast: 10,
    ppSaveAsDefault: 11,
    ppSaveAsHTML: 12,
    ppSaveAsHTMLv3: 13,
    ppSaveAsHTMLDual: 14,
    ppSaveAsMetaFile: 15,
    ppSaveAsGIF: 16,
    ppSaveAsJPG: 17,
    ppSaveAsPNG: 18,
    ppSaveAsBMP: 19
    };


    // --------------------------------------------------------------


    /**
    * pptファイルをppsでsaveasする
    *
    */
    function SaveAsPPS() {
    this.initialize.apply(this, arguments);
    }

    SaveAsPPS.prototype = {
    initialize: function() {
    this.fso = WScript.CreateObject("Scripting.FileSystemObject");
    },

    /**
    * 1ppt分の処理
    *
    */
    saveOne: function(app, options, fn_ppt) {
    var dir_out;
    if(options.samefolder) {
    // 元pptと同じフォルダに出力
    dir_out = this.fso.GetParentFolderName(fn_ppt);
    } else {
    // 出力先が明示されている
    dir_out = options.out;
    }

    if(!this.fso.FolderExists(dir_out)) {
    this.fso.CreateFolder(dir_out);
    }

    var prs = app.Presentations.Open(fn_ppt, true, true, false);

    try {
    var fn_out = this.fso.BuildPath(dir_out, this.fso.GetBaseName(fn_ppt) + ".pps");
    prs.SaveAs(fn_out, PpSaveAsFileType.ppSaveAsShow, options.embedfont);
    } catch(ex) {
    throw ex;
    } finally {
    prs.Close();
    }
    },

    /**
    * main
    *
    * @param argv WScript.Arguments
    */
    main: function(argv) {
    // オプション
    var opt = argv.Named;

    var options = {
    // 出力先フォルダ
    out: ".",
    // 保存先を元pptと同じにするflag
    samefolder: true,
    // フォントを埋め込むかどうか
    embedfont: true
    };

    if(opt.Item("nofont")) {
    options.embedfont = false;
    }

    if(opt.Item("out")) {
    options.out = opt.Item("out") ;
    options.samefolder = false;
    }
    options.out = this.fso.GetAbsolutePathName(options.out);

    var ppt_app = new ActiveXObject("PowerPoint.Application");

    try {
    // 引数
    var args = argv.Unnamed;
    for(var i = 0 ;i < args.length; i++) {
    var fn_ppt = this.fso.GetAbsolutePathName(args(i));
    if(this.fso.FolderExists(fn_ppt)) {
    // 引数がフォルダなら、当該フォルダ内のpptを処理対象に
    var folder = this.fso.GetFolder(fn_ppt);
    for(var it = new Enumerator(folder.Files); !it.atEnd(); it.moveNext()) {
    var file = it.item();
    if(file.Name.match(/\.ppt$/i)) {
    fn_ppt = file.Path;
    this.saveOne(ppt_app, options, fn_ppt);
    }
    }
    } else if(this.fso.FileExists(fn_ppt)) {
    // 引数がファイルの場合
    this.saveOne(ppt_app, options, fn_ppt);
    } else {
    WScript.echo("*** ppt not found: " + fn_ppt);
    return false;
    }

    }

    } catch(ex) {
    throw ex;
    } finally {
    ppt_app.Quit();
    }

    }
    }

    // --------------------------------------------------------------

    var app = new SaveAsPPS();
    app.main(WScript.Arguments);


  • JUDE APIを jrubyから使う その2

    JUDE 5.3には編集APIのサンプルとしてDBリバースのプログラムが付属しています。
    サンプルといっても十分便利。DB接続可能なときはこれを利用している。


    何かしらのシステムやアプリを引き継ぐと、DDLすら提示されず、「テーブル定義書」「テーブル設計書」といった名称のxlsブックだけ出てくる場合がある。困ったことによくある。
    目視でぽちぽちjudeにエントリしてもいいのだけど、編集APIのプラクティスを兼ねてインポートツールを書いた。

    要するにCSVで記述したERエンティティをjudeモデルとして一括登録、というもの。
    ※注意:judeファイルになんらか問題が発生しても責任を負いかねます。自己責任で。そしてバックアップ or リビジョン管理重要。

    judeユーティリティ


    1. xlsからCSVを作成する
      • 記述例はアーカイブ同梱のsamp1.xlsを参照
      • 「テーブル定義書」のレイアウトは様々。それに合わせて以下のようなCSVファイルを出力するようマクロを調整のこと
        • 1CSVファイル=1エンティティ
        • 1行=1属性
      • CSVファイル内の最初の列が「#」で始まる行はメタ情報行
        • #@entity行
          • 1列目:エンティティの論理名
          • 2列目:エンティティの物理名
        • 次に#で始まる行は各列の値の意味を指定するヘッダ。認識するヘッダは以下の通り
          logicalname属性の論理名
          physicalname属性の物理名
          domain属性のドメイン(の論理名)
          更新対象judeファイル内に同名の論理名を持つドメインがない場合は作成される
          type属性のデータ型
          更新対象judeファイル内に同名のデータ型がない場合は作成される
          length属性の長さ
          nnNOT NULLかどうか
          非空文字ならNOT NULL指定
          default属性のデフォルト値
          pkPRIMARY KEY制約をつけるかどうか
          非空文字ならPRIMARY KEY指定
      • excelがインストールされたwindows上で同梱のmake_csv.jsマクロ実行し、全シートをCSV出力します
        • 出力ファイル名は"シート名.csv"になります。
          シートごとに別ファイルに出力されます

          > cscript.exe make_csv.js samp1.xls
      • 編集APIの仕様で以下の点に注意
        • 論理名のないエンティティは登録できない
        • 論理名のない属性は登録できない
        • 同じ論理名を持つエンティティが既に存在すると登録できない
        • データ型に英小文字を指定しても英大文字になる
          • GUIでは英小文字名称のデータ型を登録できるけど、編集APIでcreateすると大文字になる。
    2. 出来たCSVをjudeファイルにインポートする
      • 実行例

        $ env CLASSPATH=./jude-pro.jar jruby ./import_ermodel.rb --fs=, -o new.jude samp2.csv
        ※インポートのデフォルトのフィールド区切りは「HT」です。--fsでカンマを指定しています。
        ※jude-pro.jarがカレントにある前提
      • 存在しないjudeファイルを指定してもよいし、既存のjudeファイルを指定してもよいです。
        存在しないjudeファイルを指定するとjudeファイルが作成されます。
        新規judeファイルに出力しインポート後の内容を確認して問題なさそうならマージするという手順がいいと思う。
      • 本スクリプトは日本語環境で作成されたjudeファイルを前提としています。
        編集APIサンプルのように createERModel(project, "ER Model");でIERModelを作成すると、日本語環境GUIでマージできなかった。日本語環境GUIで作ったjudeファイルの場合、IERModelの名称が"ERモデル"(これはエクスポートするとわかる)となっていることに関係するのではないかと想像している。

    ■実行環境
     OS: CentOS 5.2 i386
       JRE: Sun Java 1.6.0_07
         jruby: 1.1.4
         JUDE-Pro: 5.3.0

    モデル編集APIは、「図要素」を編集できるようになるとうれしい。図中のモデルの位置だとか大きさ。GUIの自動レイアウトがもうひとつだなぁと感じている。何かしらのレイアウトエンジンを使って独自の整列ができるようになったらかなり素敵だと思うんだけどな。


  • CakePHPの Security.levelとセッション

    JSONPでやりとりするウェブアプリをCakePHPで作っていた。
    任意のウェブページに埋め込めるタイプのもので、サービスに対してセッションを維持する仕様(POST用の使い捨てkeyを保持する)。

    しかし、何かサービスにアクセスするたびに、セッションが破棄・生成されているようだ。

    Security.levelがhighに設定されていると、セッション「ID」は毎回生成されるのがCakePHPの仕様。

     * CakePHP session IDs are also regenerated between requests if
     * 'Security.level' is set to 'high'.
    

    だけどセッション自体は維持されるはず。。。
    結局フレームワークのソースを読むことに。

    highとmediumの場合、セッションが有効かどうか判断するのにreferrerも勘案されるらしい。
    んで、クロスドメインなJSONPなウェブアプリだったため、referrerがinvalidになって毎回破棄、ということのようだ。

    結局、lowを採用、Session.timeoutを小さくして調整することにした。

    ■cake/libs/session.php
        function __initSession() {
            switch($this->security) {
                case 'high':
                    $this->cookieLifeTime = 0;
                    if (function_exists('ini_set')) {
                        ini_set('session.referer_check', $this->host);
                    }
                break;
                case 'medium':
                    $this->cookieLifeTime = 7 * 86400;
                    if (function_exists('ini_set')) {
                        ini_set('session.referer_check', $this->host);
                    }
                break;
                case 'low':
                default:
                    $this->cookieLifeTime = 788940000;
                break;
    
    

    php:5.1.6
    CakePHP:1.2.0.7296-rc2




  • JUDE Community版より Professional版がよいと思うこと

    JUDEには無償のCommunity版と有償のProfessional版がある。
    両者の違いは製品公式ページの機能比較表のとおり。
    結構Community版使ってますみたいなエントリを見かけるんだけども、自分がPro版購入を決めたポイントを並べてみる。
    ※僕はチェンジビジョンの関係者ではありません。

    1. EMF出力できる
      • 特にコピー&ペーストでWord等office文書に貼り付けられるところがいい。
      • ビットマップでなくベクターグラフィックだから、拡大縮小しても印刷しても綺麗。貼り付け先文書内で好きな大きさにできる。
    2. ステレオタイプのアイコンを指定できる
      • 機能比較表ではさらっと書いてあるけど、これはかなりうれしい。
      • boundary(メロンが横になった記号)やactor(棒人間)に帳票や人を示すビットマップを指定すると「普通の人」も見る気になってくれる
        icon.png
      • オブジェクトごとにUML表記/アイコン表記を切り替えられるので、読み手に合わせた作図をしつつ、情報量はキープみたいな使い方。
    3. モデルから関連する図へジャンプできる
      • これ
      • このモデルが使われているあの図はどこだっけ・・、とモデル基点で図を逆引きできるのは地味に便利。
    4. CRUDマトリクスを書ける
      • ファンクションポイント法による見積りのソースに使えてうれしい
      • Com版とPro版の違い、とは関係ないが、CRUDの軸に指定したユースケース図にユースケースを足したとき、ER図にモデルを足したときに、CRUD表の当該モデルに対する行/列が自動で追加されるのは本当にうれしい機能だ。
    5. 別名をつけられる
      • クラスの名前や属性の名前など、日本語表記と英語表記を付けることが出来る。
      • モデリングは日本語で行い、SQL出力など英語表記にしたい部分だけ英語、という使い分けができて助かる
    6. APIの違い
      • ER図(系のモデル)、CRUDの情報を取れるのはかなりのアドバンテージ
    7. プロジェクトをマージできる
      • judeを2つ起動して、片方のjudeからもう片方にコピー&ペースト、ということが「出来ない」ので、その代わりとして使えるから。

    ちょっとした違いかもしれないけど、普段の業務でしみじみ便利だなーと思う瞬間。
    こういう違いを知らずにCommunity版を使ってる人がいたらちょっともったいないな、と思ったので書いてみた。