Stellaqua - TOMの技術日記 このページをアンテナに追加 RSSフィード

2010年08月08日

[]Amazon EC2上のHadoopMeCabを使えるようにしてみた…い

最近すっかり"Webサービスを作ってみた"系の記事ばっかりでしたが、今回は久々にEC2上でのHadoopのお話。

以前の記事EC2上でHadoopを動かした時は、MeCabデフォルトで入っていなかったので、事前に自宅サーバ上で分かち書きしたデータを使っていました。

ただ、日本語処理するならMeCabはほぼ必須だろうし、せっかくならMeCabが使える状態でMapReduceしたいのが人情ってもんです。

という訳で、EC2上のHadoopを使って、MeCabを利用した日本語文章の単語カウントMapReduceに挑戦してみました。

基本的な方向性としては以下の通りです。

  1. EC2上でインスタンス起動。
  2. 起動したインスタンスMeCabインストール
  3. AMIとして保存。
  4. Hadoopを起動する時にこのAMIが使われるようにする。

で、実際にやってみた訳なんですが、結論から言うとうまくいきませんでした…。

AMIを保存して、そのAMIが使われるようにするところまではできたんですが、そのインスタンスログインできない状態になってしまって、実際にMeCabを利用したMapReduceを動かすところまでできていません。

という事で途中までではありますが、やってみた手順を書き残しておきたいと思います。

以下、"[local]$"がローカルのサーバ上、"[ec2]$"がEC2上でのプロンプトを表す事とします。

EC2上でHadoopを起動してログインする

まずはEC2上にインスタンスを1つ起動させてログインします。

[local]$ hadoop-ec2 launch-master hadoop-test 1
[local]$ scp -i stellaqua.id *.pem root@ec2-XXX-XXX-XXX-XXX.compute-1.amazonaws.com:/mnt
[local]$ hadoop-ec2 login hadoop-test

タイムゾーンを変更する

今回(というか毎度)参考にさせて頂いているid:rx7さんの記事によるとタイムゾーンの設定をしておいた方がよいとの事なので、ついでにここでタイムゾーンの変更をしておきます。

[ec2]$ cp /usr/share/zoneinfo/Japan /etc/localtime
cp: `/etc/localtime' を上書きしてもよろしいですか(yes/no)? y

MeCabインストールする

続いて本命のMeCabインストールします。

[ec2]$ yum -y update
[ec2]$ yum -y install mecab mecab-devel mecab-ipadic
[ec2]$ echo 'こんにちは' | mecab
こんにちは      感動詞,*,*,*,*,*,こんにちは,コンニチハ,コンニチワ
EOS

ちゃんとMeCabが使えるようになりましたね!

EC2root権限が与えられるので、yumで必要なものをガシガシ入れてしまうとよいですね。

AMIを作成してS3にアップロードする

AMI作成の手順については、毎度お世話になっているid:rx7さんの記事を参考にさせて頂きました。

Amazon EC2/S3を使ってみた - 3.EC2起動後〜AMI作成 - 元RX-7乗りの適当な日々

[ec2]$ cd /mnt/
[ec2]$ ec2-bundle-vol -d /mnt --privatekey pk-XXXX.pem --cert cert-XXXX.pem --user XXXX-XXXX-XXXX -p hadoop-0.17.0-i386 -r i386
[ec2]$ ec2-upload-bundle --bucket stellaqua/ec2_images/hadoop_mecab --manifest hadoop-0.17.0-i386.manifest.xml --access-key XXXX --secret-key XXXX
[ec2]$ exit
[local]$ hadoop-ec2 terminate-cluster hadoop-test
[local]$ ec2-register stellaqua/ec2_images/hadoop_mecab/hadoop-0.17.0-i386.manifest.xml
IMAGE   ami-XXXX
[local]$ vi hadoop-ec2-env.sh
→"S3_BUCKET=hadoop-ec2-images"となっているところを、上記で指定したバケットに変える

ポイントは、manifestファイルをHadoopのバージョンとアーキテクチャの名前を付けて保存する事と、設定ファイルでAMIを保存したバケットを設定しておく事ですね。

これで、hadoop-ec2コマンドでHadoopを起動する時に、登録したAMIが使われるようになるはずです。

早速、クラスタを起動してログインしてみます。

[local]$ hadoop-ec2 launch-cluster hadoop-test 1
[local]$ hadoop-ec2 login hadoop-test

これでログインできるはずなんですが、なぜかログインしに行ったままウンともスンとも言わない状態になってしまいます。

"Permission denied"とも言われないし、netstatで見るとESTABLISHEDにはなっているので、接続自体はできているとは思うのですが…。その後も色々試してはみたんですが、現時点でお手上げ状態になっています…。

また折を見て試してみて、うまくいったら記事にしようかなと思います。

2010年05月01日

[][]コメント投票機能・Twitter連携機能など付けてみました

BEAR連載のおかげで、できる事の幅も広がってきたので、今まで作ってきたWebサービスリニューアル大作戦の第一弾として、今北川柳をBEARベースに置き換えつつ、機能拡張してリニューアルしてみました。

追加した機能のご紹介

Twitter連携

作成された川柳を、@imakitasenryuでつぶやくようにしました。フォローしてもらうと、意味が分かりそうで分からない川柳がいっぱい流れてくると思います。(笑)

面白い川柳があったら返信またはRT(QT)してもらうと、その川柳への投票になります。コメント付きで返信・RTすると、ちゃんと投票コメントとして反映されるようになっています。

返信・RTする場合、以下のような書式でないと正しくコメントを抽出できない事があるので、ご注意下さい。

コメント投票機能

今まで川柳への投票は、はてなスターの機能を使っていたんですが、いかんせん敷居が高いのか全く投票に使ってもらえなかったので(^^;、自前で投票機能を実装しました。

「面白い!」と思ったら、"天晴+"ボタンを押すと、その川柳に投票する事ができます。また、"天晴+"ボタンの隣の吹き出しボタンを押してコメントフォームにコメントを入力してから投票すると、コメント付きで投票する事もできます。

コメント表示機能

川柳を色紙画像で表示する個別表示画面で、投票されたコメントを吹き出しで表示するようにしました。鬱陶しいと思ったら"コメントを非表示にする"のリンクをクリックすれば、表示を止める事ができます。表示設定はCookieに保存されるので、次回訪れた時も設定がそのまま引き継がれるようになっています。

APIの追加

ブログなんかで簡単に紹介してもらえるように、画像のAPIは前バージョンからあったのですが、今回、テキストベースのAPIも作ってみました。現在、以下のAPIが利用可能です。

画像取得API(URLを指定)
http://www.stellaqua.com/imakitasenryu/api/images.php?url={対象サイトのURL}[&size=small]
画像取得API(IDを指定)
http://www.stellaqua.com/imakitasenryu/api/images.php?id={川柳ID}[&size=small]
テキスト取得API(URLを指定)
http://www.stellaqua.com/imakitasenryu/api/texts.php?url={対象サイトのURL}[&format=(xml|json|jsonp&callback={コールバック関数})]
テキスト取得API(IDを指定)
http://www.stellaqua.com/imakitasenryu/api/texts.php?id={川柳ID}[&format=(xml|json|jsonp&callback={コールバック関数})]
コメント取得API
http://www.stellaqua.com/imakitasenryu/api/comments.php?id={川柳ID}[&format=(xml|json|jsonp&callback={コールバック関数})][&_start={表示ページ}]

テキスト取得APIのレスポンスは以下のような感じです。(XMLの場合)

<?xml version="1.0" encoding="utf-8"?>
<results>
    <senryu>
        <id>52e85875124208d7c40ff10d16d7bae0</id>
        <createdtime>2010-05-01T14:55:14+0900</createdtime>
        <sourceurl>http://news.google.co.jp/</sourceurl>
        <sourcetitle>Google ニュース</sourcetitle>
        <votecount>0</votecount>
        <commentcount>0</commentcount>
        <texts>
            <text1>出席し</text1>
            <text2>訴えている</text2>
            <text3>謝罪する</text3>
        </texts>
        <links>
            <imageurl>http://www.stellaqua.com/imakitasenryu/api/images.php?id=52e85875124208d7c40ff10d16d7bae0</imageurl>
            <entryurl>http://www.stellaqua.com/imakitasenryu/api/texts.php?id=52e85875124208d7c40ff10d16d7bae0</entryurl>
            <commenturl>http://www.stellaqua.com/imakitasenryu/api/comments.php?id=52e85875124208d7c40ff10d16d7bae0</commenturl>
            <tweeturl>http://www.twitter.com/imakitasenryu/status/13173910635</tweeturl>
        </links>
    </senryu>
</results>

コメント取得APIのレスポンスは以下のような感じです。(XMLの場合)

<?xml version="1.0" encoding="utf-8"?>
<results>
    <pager>
        <totalResults>1</totalResults>
        <startIndex>1</startIndex>
        <itemsPerPage>1</itemsPerPage>
        <firstpageurl></firstpageurl>
        <nextpageurl></nextpageurl>
        <prevpageurl></prevpageurl>
        <lastpageurl></lastpageurl>
    </pager>
    <comments>
        <comment>
            <id></id>
            <createdtime>2010-05-01T15:00:00+0900</createdtime>
            <targetid>52e85875124208d7c40ff10d16d7bae0</targetid>
            <body>コメントしてみたよ</body>
            <targeturl>http://www.stellaqua.com/imakitasenryu/api/texts.php?id=52e85875124208d7c40ff10d16d7bae0</targeturl>
            <via>http://www.stellaqua.com/imakitasenryu/api/texts.php?id=52e85875124208d7c40ff10d16d7bae0</via>
        </comment>
        <comment>
            :
        </comment>
            :
    </comments>
</results>

レスポンスコードは、200(OK)・400(呼び出し方の不正)・403(高負荷によるアクセス規制)・500(サーバエラー)のいずれかが返ってきます。

こんなAPIを公開したところで何かに使えるのかどうか全くもって不明ではありますが、まぁこれを使って何かを作ろうという奇特な方が現れた時の為に公開しておきます。(笑)

ちなみに、川柳を作成する対象URLを指定してアクセスした時に、対象サイトの内容のキャッシュが無い場合、レスポンスに数秒掛かる事があるのでご注意下さい。

その他こだわった点とか

細かい事なんですが、今回のリニューアルに伴ってタイトルからα表記を外して、ついでにfaviconも作ってみました。ドットを置いていくタイプのfavicon作成Webサービスで、5分くらいで作ったやっつけにしてはそれなりなモノができたので満足しています。

あと、コメントの表示は結構こだわってみました。ボワンと現れて、スーっと消えていく感じが結構気にいっています。最初はニコニコ動画風な流れるコメントにしようかとも思ったんですが、何か今や目新しさも無くなった感もあるし、あんまりやり過ぎても鬱陶しくなるだけかなと思って、今の形に落ち着きました。

この辺りのアニメーションと、Ajaxのデータのやり取りでjQueryをふんだんに使ってみたんですが、色々と勉強になりました。Webサービスにちょっとした彩りを添えるのにはjQueryは使い易くって非常に良いですね。

それから、川柳の元になる単語を拾ってくる時に、数字の読みをきちんと行うようにしてみました。

MeCabで普通に数字を読ませると一桁ずつでしか読んでくれないので、"123"を"イチニサン"と読んでしまうんですが、こういうのをちゃんと"ヒャクニジュウサン"と読めるように、自前で専用のクラスを作って変換をするようにしてみました。これで、数字を含む単語でもそれなりに抽出できるようになったのではないかと思います。

その他困った点とか

TwitterOAuth対応は以前に試した事があったので、そこでの苦労はあまり無かったんですが、RTを取り込んで投票に反映させる機能が大変でした…。

以下、ハマりどころ…。

  • 公式RTは、自分のつぶやきの内のどのつぶやきがRTされたかは分かるけど、誰からRTされたのかはAPIで取得できない…。(仕方がないので、公式RTを投票に使うのは諦めた。)
  • 非公式RTだと書式が決まってないから、コメント部分だけ抜き出そうとすると、どうするのがベストか分からない…。(仕方がないので、最初に出てきた"RT"より前の部分をコメントと決め打ちしちゃう事にした。)
  • 非公式RTだと、どのつぶやきに対する返信か分からないから、どの川柳に対する投票なのか特定できない…。(仕方がないので、元のつぶやきに含ませてあるURLから川柳のIDを取得する事にした。)
  • 非公式RTで元のつぶやきのURLを短縮URLにしないで入れてもらおうとすると、コメントに使える文字数が少なくなってしまう…。(仕方がないので、短縮URLの場合は展開してからIDを取得する実装を入れた。)

Twitterも今やインフラとしての利用価値が大きいから、できるだけTwitterからもWebサービスに参加できる間口は広げたいんですが、自由度が高過ぎる故に、Webサービスとして欲しい情報を拾ってこようと思うと妥協しなきゃいけない部分が多くて悶々としますな…。

Twitter連携は突っ込むと泥沼にハマりそうな感もあるので、そこそこの線で妥協しときたいと思います…。


そんな訳で、色々と機能追加してより遊べるようになったかと思うので、「これからも 今北川柳 よろしくね♪」(一句)

*1:"RT"or"QT"が1回("RT:"とかコロン付きでもOK)、コメントが"RT"or"QT"より前にある、元のつぶやきの中のURLが含まれる(短縮URLでもOK)、という条件を満たしていれば大丈夫なはずです。

2010年03月26日

[]はてなキーワードからMeCabのユーザ辞書を作る

今まで作ったWebサービスのいくつかでMeCabを使っているんですが、最近の言葉がMeCabの辞書に反映されていないので、特に話し言葉に近いブログとかの単語判別が甘いという悩みがありました。

そんな訳で、以前からずっとやろうと思っていたまま後延ばしにしていた、はてなキーワードからMeCabのユーザ辞書を作って利用するようにする作業を行いました。

手順などは、以下のサイトを参考にさせてもらいました。

はてなキーワードからMecCab辞書を生成する(Ruby版)

以下、実際に行った手順。

はてなキーワードファイルをダウンロードする

以下のページからはてなキーワードがまとまって入っているCSVファイルをダウンロードします。

はてなダイアリーキーワードふりがなリストを公開しました - はてなダイアリー日記

$ wget "http://d.hatena.ne.jp/images/keyword/keywordlist_furigana.csv"

辞書作成用のCSVファイルを作成する

最初は上記Ruby版の変換スクリプトをそのまま使わせて頂いたのですが、スワップを使い切ってしまうぐらいメモリを食ってしまって処理が止まってしまうという事態になってしまった為、PHPでスクリプトを書き直して変換処理を掛ける事にしました。

#!/usr/bin/php
<?php
$fd_in = fopen('keywordlist_furigana.csv', 'r');
$fd_out = fopen('hatena.csv', 'w');

$i = 0;
while ( !feof($fd_in) ) {
    // EUC-JPのファイルなので、使用しているUTF-8に変換(ついでにtrim)
    // システム辞書でEUCを使用している場合は、toutf8は削ってください
    $line = trim(mb_convert_encoding(fgets($fd_in), 'utf-8', 'euc-jp'));
    // タブ区切り(仮名\t単語)になっているので、split
    $words = explode("\t", $line);
    if ( count($words) < 2 ) { continue; }
    $kana = ( $words[0] == '' ) ? '*' : trim($words[0]);
    $word = trim($words[1]);

    // 日付が入ったワードは、不要なものが多いので外す
    $pattern = '/[0-9]{4}(\/|\-)[0-9]{2}(\/|\-)[0-9]{2}/';
    if ( preg_match($pattern, $word) === 1 ) { continue; }
    $pettern = '/[0-9]{4}年/';
    if ( preg_match($pattern, $word) === 1 ) { continue; }
    $pattern = '/[0-9]{1,2}月[0-9]{1,2}日/';
    if ( preg_match($pattern, $word) === 1 ) { continue; }

    // 制御文字、HTML特殊文字が入ったものは外す
    $pattern = '/[[:cntrl:]]/';
    if ( preg_match($pattern, $word) === 1 ) { continue; }
    $pattern = '/\&\#/';
    if ( preg_match($pattern, $word) === 1 ) { continue; }

    // はてなという言葉が入ってるものは、運用の為のワードが多いので削除
    // 一部、正しい用語も消してしまっているので、用途によっては下行をコメントアウト
    $pattern = '/はてな/';
    if ( preg_match($pattern, $word) === 1 ) { continue; }

    // MeCabでパース
    $nodes = explode("\n", `echo '{$word}' | mecab --unk-feature='未知語'`);

    $node_count = 0;
    $unk_count = 0;
    $area_count = 0;
    $name_count = 0;

    // ノードと種類をカウント
    foreach ( $nodes as $node ) {
        $result_mecab = explode("\t", $node);
        $node_word = trim($result_mecab[0]);
        $feature = explode(',', $result_mecab[1]);
        // BOS/EOSはスキップ
        if ( $node_word === 'BOS' || $node_word === 'EOS' ) { continue; }
        $area_count += ( trim($feature[2]) === '地域' ) ? 1 : 0;
        $name_count += ( trim($feature[2]) === '人名' ) ? 1 : 0;
        $unk_count += ( $node_word === '未知語' ) ? 1 : 0;
        $node_count++;
    }

    // node数が1つ(システム辞書で1語として解析可能)の場合は登録しない
    if ( $node_count <= 1 && $unk_count === 0 ) { continue; }
    // 全nodeが地域名だった場合は、登録しない(東京都北区は、東京都 | 北区で分けたい為)
    if ( $node_count === $area_count ) { continue; }
    // 全nodeが人名だった場合は、登録しない(相田翔子は、相田 | 翔子で分けたい為)
    if ( $node_count === $name_count ) { continue; }

    // コストの計算
    $cost = -400 * pow(mb_strlen($word, 'utf-8'), 1.5);
    if ( $cost < -36000 ) { $cost = -36000; }

    // 平仮名を片仮名に変換
    $kana = mb_convert_kana($kana, 'C', 'utf-8');

    // 行出力
    $output = "$word,1345,1345,$cost,名詞,一般,*,*,*,*,$word,$kana,$kana\n";
    fputs($fd_out, $output);

    // 英字の場合は、小文字統一、大文字統一も出力しておく
    if ( $word !== strtolower($word) ) {
        $output = strtolower($word).",1345,1345,$cost,名詞,一般,*,*,*,*,$word,$kana,$kana\n";
        fputs($fd_out, $output);
    }
    if ( $word !== strtoupper($word) ) {
        $output = strtoupper($word).",1345,1345,$cost,名詞,一般,*,*,*,*,$word,$kana,$kana\n";
        fputs($fd_out, $output);
    }

    $i++;
    if ( $i % 1000 === 0 ) {
        echo "{$i}件目を処理\n";
    }
}
fclose($fd_in);
fclose($fd_out);
?>

基本ベタ移植で、コメントもほぼそのまま流用させて頂きました。

(追記(2010/3/27 01:10) 平仮名を片仮名に変換するところでエンコーディングを指定し忘れていたのでコードを修正しました。あとCSVへの出力形式が間違っていたのでそちらも修正しました。)

MeCab用ユーザ辞書に変換する

できあがったCSVファイルから、以下のようにMeCab用ユーザ辞書に変換します。

$ /usr/lib/mecab/mecab-dict-index -d `awk '/^dicdir/{print $3}' /etc/mecabrc` \
-u hatena.dic -f utf-8 -t utf-8 hatena.csv

mecab-dict-indexのパスは環境によって違うと思うので適宜置き換えて下さい。また、システム辞書のパスも環境によって違うと思うので、/etc/mecabrcから取ってくるような書き方にしてみました。

ユーザ辞書が使われるように設定する

/etc/mecabrcに以下の行を加えると、MeCabを実行した時に必ずユーザ辞書が使われるようになります。

userdic = /path/to/hatena.dic

またはMeCabコマンド実行時にユーザ辞書を指定する事もできます。

$ echo "けいおん二期ktkr" |mecab
けい    名詞,一般,*,*,*,*,けい,ケイ,ケイ
おん    名詞,一般,*,*,*,*,おん,オン,オン
二      名詞,数,*,*,*,*,二,ニ,ニ
期      名詞,接尾,助数詞,*,*,*,期,キ,キ
ktkr    名詞,固有名詞,組織,*,*,*,*
EOS
$ echo "けいおん二期ktkr" |mecab -u /path/to/hatena.dic
けいおん        名詞,一般,*,*,*,*,けいおん,ケイオン,ケイオン
二      名詞,数,*,*,*,*,二,ニ,ニ
期      名詞,接尾,助数詞,*,*,*,期,キ,キ
ktkr    名詞,一般,*,*,*,*,ktkr,キタコレ,キタコレ
EOS

自宅サーバの方には反映済みなので、MeCabを使っているWebサービス(今北川柳とか、あいウェ文とか)は、若干単語の判別性能が上がってるんじゃないかなと思います。

2009年12月31日

[][]文章を落ち物パズルにしちゃうWebサービスを作ってみた

今年最後の作ってみたシリーズは、入力された文章を元に、文字が降ってくる落ち物パズルを生成するWebサービスです。

文字積み

何とか年内リリースにぎりぎり間に合いました…。

これは何?

入力された文章をひらがなに分解して、1文字1ブロックにみたてた落ち物パズルにしちゃうWebサービスです。

落ち物パズルと言っても自分で操作する訳ではなく、"あいうえお"の各段がフィールドの各列に対応して降ってくるので、リプレイが生成されるようなイメージになります。

ルールとしては、ぷよぷよ的な感じですが、以下のようにだいぶ緩い感じになっています。

  • 同じ行の文字("あ"と"う"とか、"に"と"ね"とか)が縦横で2つ以上隣接すると消える。
  • 濁点付き・拗音などは同じ行とみなす。("ばびぶべぼ"・"ぱぴぷぺぽ"はハ行、"ゃゅょ"はヤ行など)
  • 連鎖はありで、得点はぷよぷよの計算式を元に独自の調整を加えた式により計算される。

れっつ連鎖!

利用はとっても簡単。文字積み作成画面で適当な文章を入力したら"ブロック生成"を押してブロックを生成して、"プレビュー"で実際のリプレイを確認、納得のいくリプレイができたら投稿!

"ブロック生成"はMeCabを使った形態素解析により読みを決めている為、思ったような読みにならない場合もあるので、そういう場合は生成されたブロックを修正する事もできます。

きちんと計算して生成ブロックを書けば簡単に連鎖数を伸ばす事はできると思いますが、意味の通る文章で、かつ大連鎖のリプレイを作るのはなかなか難しいんではないかと思います。

ぜひとも大連鎖を起こせる文章に挑戦してみて下さいね。

アイデア元

ある日、@nifty:デイリーポータルZで、"ドレミファどの音が多い?"という記事を読みまして、「これWebサービスにしたら面白いんじゃね?」と思ったのがアイデアの元になっています。

最初は、これをそのままパク参考にさせてもらって、歌を投稿すると、落ち物パズルのリプレイが生成されるみたいなWebサービスにしようかと思ったんですが、以下のような理由から方向転換…。

  • 楽譜と歌詞を入力するのは面倒くさそう。
  • 歌詞とかって、著作権的にアウトな気がする。

で色々考えている内に、音階の代わりに文章を"あいうえお"の各行に分解する事を思い付いて、さっくりと作ってみました。

Flash部分をもっとポップで派手な感じに作れたら、見て楽しいサービスになったと思うんですが、とりあえず動作するものを作るとこまでで精一杯でした…。この辺りのセンスが欲しい今日この頃…。orz

ただ、どちらかと言うと、大連鎖を起こすような文章を作るのを競い合うっていうのが面白さなんではないかと思うので、いつかすごい職人さんが現れる事を期待しています。(笑)


では、よいお年を〜。

2009年08月31日

[][]Webサイトやブログの文章を元に川柳を自動生成するサービスを作ってみた

すっかり、"作ってみた"シリーズの公開ブログになっていますが、また、どうでもいいようなアイデアを思い付いてしまったので作ってみました。

今北川柳

ネーミングセンスがアレなのは気にしない方向で…。*1

これは何?

WebサイトやブログURLを入力すると、文章から適当に単語を拾ってきて川柳をでっちあげるサービスです。

ただ文字列として生成するだけだと見た目的に面白くないので、色紙に毛筆で書いたっぽい画像を生成するようにしました。

試しにこのブログでやってみたら、こんな感じになりました。

全くもって中身がありませんね。(笑) これだけだとなんなので、ついでにasahi.comのトップ画面でやってみたら、こんなのになりました。

総選挙直後なので、やっぱり"総選挙"という単語が入ってきてますね。まぁ、やっぱり意味はサッパリ分かりませんが。(笑)

どんな仕組みなの?

川柳生成部分

基本的な仕組みとしては、対象の文章から5文字・7文字の言葉を拾ってきて、5・7・5に並べ替えているだけです。とは言え、一応まともな単語が出現するように、色々と工夫しています。

とりあえず、元の文章から日本語部分だけを拾う為に、半角の英数記号はばっさり捨てています。なので、"PHP"とか文章に含まれていたとしても、川柳に入ってくる事はありません。本当は英単語とか略語とかも単語の候補として拾えるといいんですけど、そういった単語の読みを取得する術がないので、仕方ないところですね…。

次に文章を単語に分解する訳ですが、最初は例によってMeCabたんに活躍してもらおうかと思ったんですが、MeCabで文章を解析してから、品詞も考慮して単語の候補を作ろうと思うと、とてもじゃないけど8秒ルールとかで収まるようなレスポンスにならなかったので、他の方法を探す事にしました。

必要なのは、"文章を文節ごとに区切る事ができる"という事と、"単語の読みが取得できる"という事で、これに関しては、CaboChaというまさにうってつけなライブラリがあるので、そちらを使う事にしました。

CaboChaで文章を解析すると、次のような形で文節の区切りやら、読みやらが得られます。

吾輩は猫である。名前はまだ無い。
* 0 1D 0/1 1.378671
吾輩    名詞,代名詞,一般,*,*,*,吾輩,ワガハイ,ワガハイ   O
は      助詞,係助詞,*,*,*,*,は,ハ,ワ    O
* 1 4D 0/2 0.000000
猫      名詞,一般,*,*,*,*,猫,ネコ,ネコ  O
で      助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ   O
ある    助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル       O
。      記号,句点,*,*,*,*,。,。,。      O
* 2 4D 0/1 0.000000
名前    名詞,一般,*,*,*,*,名前,ナマエ,ナマエ    O
は      助詞,係助詞,*,*,*,*,は,ハ,ワ    O
* 3 4D 0/0 0.000000
まだ    副詞,助詞類接続,*,*,*,*,まだ,マダ,マダ  O
* 4 -1D 0/0 0.000000
無い    形容詞,自立,*,*,形容詞・アウオ段,基本形,無い,ナイ,ナイ  O
。      記号,句点,*,*,*,*,。,。,。      O
EOS

この結果から、一旦、文節の読みの長さ毎に配列に突っ込んでいって、5文字の配列から2つ、7文字の配列から1つ単語を取得して、川柳を生成しています。

ただし、5文字・7文字の単語が1つも無い場合のみ、プラスマイナス1文字の字余り・字足らずも許すようにしています。

あと、CaboChaの結果の中に係り受けの点数があるので、それを使って、いっぱい係られているほど単語として選ばれやすくなるように重み付けをしていたりもします。

イメージ生成部分

背景は自前で用意しておいて、ImageMagickを使って文字を載せています。

ただ、ImageMagickで縦書きする方法が分からなかったので、1文字ずつ分解して、座標指定で無理やり縦書きにしています。

拗音・促音だと幅が違うので、それっぽい場所に収まるように調整していたりとか、伸ばし棒(ー)の向きが縦にならないので、縦棒(|)にして誤魔化していたりとかしてるので、若干おかしな感じになる場合もありますが、まぁそれっぽい画像にできたんじゃないかなぁ、とそれなりに満足しています。

毛筆のフォントはフリーで使えるものを探してきて、利用させて頂きました。

404 Not Found

こんなクオリティのものをフリーで使えるとは素晴らしい!

雑感

元々、文章要約の一つの形として川柳とか面白いかな、と思って作ってみたんですが、選択された単語同士に意味的な繋がりが無いので、いかんせん意味を持った川柳になる可能性が低いのが難点ですね。

もっと精度の高い川柳を生成しようと思うと、人工知能的なアプローチが必要になりそうです。それはそれで面白そうではあるので、またチャレンジしてみたいところではありますが。


という訳で、ぜひとも遊んで みて下さい!(七五調、一部字余り)

*1:元ネタは言うまでもなく、今北産業。