Unicodeのすべての文字を1ページで

この記事で作るもの:Unicode 6.0のすべての文字を1ページで (PDF, 23MB)

4327377368ヨハネス・ベルガーハウゼン, シリ・ポアランガン『世界の文字と記号の大図鑑 ー Unicode 6.0の全グリフ』(研究社, 2014)の著者はUnicode 6.0の全109242文字2時間30分かけて見るビデオの作者ですか。今度は1024ページの書籍。「Decodeunicode」で画像検索すると原著が出てきますが、楽しみですね。(追記:世界の文字と記号の大図鑑

「Unicodeのすべての文字を印刷した本って、前にもなかったっけ?」と思って本棚を探したのですが、勘違いでした。

0321480910頭に浮かんだのはThe Unicode Consortium『The Unicode Standard, Version 5.0』(Addison-Wesley Professional, 2006)(文献リストあり、索引なし)だったのですが、この本は、(1) The Unicode Standard、(2) Code Charts (PDF)から漢字を除いたもの、(3) Han Radical-Stroke Index (PDF)という構成でした。つまり、漢字はコードポイント順に一つずつではなく、Han Radical-Stroke Indexという形で掲載されているだけでした。

Unicode 5.0より後は、紙媒体ではないようですが、たとえば6.0について同じことをするなら、 (1) The Unicode Standard (PDF)、(2) Code Charts (PDF)、(3) Han Radical-Stroke Index (PDF)を使うことになるのでしょう。(Code Chartsには漢字も含まれているので、「文字の一覧(番号順)」が欲しいだけならCode Chartsだけで十分です。)

これらの資料もいいのですが、こういうのは、自分でもちょっとやってみたいところです。例えば、Unicodeの全文字を1ページに、なんてことも、自分で作れるようになればできます。

というわけで、必要なデータをEnumerated Versions of The Unicode Standardから探して作ろうとしたら、これがなかなか面倒でした。

Unicode 6.0の全文字は、UnicodeData.txtに載っているはずですが、この第1列をHTMLの数値文字参照に置き換えるだけでは終わりません。

UnicodeData.txtには、CJK統合漢字など、1行1文字になっていない部分があります。それを補うのは面倒なので、Unicode 5.1以降で導入されたUnicode Character
Database in XML
を使うことにします。このXMLファイルは、char要素1個が1文字に対応します。

XMLファイルには、印刷できない文字?も含まれているので、それを除外しなければなりません。Unicode 6.0のコードポイントは109449個ありますが、そのうちGraphic Characterは109242個です(参照)。『世界の文字と記号の大図鑑』の109242文字というのはこれのことなのでしょう。Graphic Characterの定義General_Category ValuesThe Unicode Standard Chapter 2 General Structureの「Table 2-3. Types of Code Points」を見ると、char要素のgc属性値がCから始まるものとZl, Zpになっているものは除外しなければならないことがわかります。(異体字セレクタのような、明らかに印刷不能な文字が残るのですが、とりあえずはそのままにします。それを使う異体字もここでは数えません。)

というわけで、ちょっとスクリプトを書いて、Unicode 6.0の文字の一覧を作ります。

こんなHTMLファイルです。このファイルは、Unicodeの文字を数値文字参照で書いてあるだけのものなので、文字を実際に表示できるかどうかは環境によります。Noto花園明朝などを入れた環境で、Firefox 31とChrome 36を試したところ、FirefoxはUnicode 6.0のすべての字形を表示できたようですが、Chromeはぜんぜんだめでした(目視以外の確認法がわかりません)。

Windows上のFirefoxでAdobe PDFに印刷した結果が冒頭のPDFファイルです。

完成に必要な無料フォントを列挙する(あるいはもっとよい方法の提案)という自由研究を、どこかの小学生がやってくれることを期待します。

関連:Unicodeのすべての文字を1回ずつ使って絵を描く

追記:The Unicode Map Projectがすばらしいです。

病的な計算式?(『ソフト・エッジ』から)

4621053833中島 震・みわ よしこ『ソフト・エッジ: ソフトウェア開発の科学を求めて』(丸善出版, 2013)で、数値計算の面白い例が紹介されていました。(参考文献リストあり。索引なし。)

精度を向上させれば近似はよくなるというのが、自然な考え方でしょう。でも、この直観に当てはまらない「病的な計算式」の例があります。(p.132)

ということで、f(a, b)=(333.75-a**2)*b**6+a**2*(11*a**2*b**2-121*b**4-2)+5.5*b**8+a/(2*b)としたときの、f(77617, 33096)が紹介されています。単精度・倍精度・4倍精度の浮動小数点数での計算結果は約1.17で、正しい値(約-0.827)とは符号も違ってしまうとのことです。

これだけ読むと、精度を向上させても近似はよくならないと思うかもしれませんが、そんなことはないはずです。実際、精度をある程度高くすれば、正しく計算できます。

Pythonで試します(ideone.comでも動きます)。

from decimal import getcontext, Decimal
for n in range(16, 40):
  getcontext().prec = n
  a = Decimal("77617")
  b = Decimal("33096")
  print(n, (Decimal("333.75")-a**2)*b**6+a**2*(11*a**2*b**2-121*b**4-2)+Decimal("5.5")*b**8+a/(2*b))
16 1.000000000000000E+21
17 -4.0000000000000000E+20
18 -2.00000000000000000E+19
19 2000000000000000001
20 -99999999999999998.827
21 1.17260394005317863186
22 -999999999999998.8273961
23 100000000000001.17260394
24 10000000000001.1726039401
25 -3999999999998.827396059947
26 -99999999998.827396059946821
27 -19999999998.8273960599468214
28 -999999998.8273960599468213681
29 100000001.17260394005317863186
30 20000001.1726039400531786318588
31 -999998.8273960599468213681411651
32 300001.17260394005317863185883490
33 -9998.82739605994682136814116509548
34 -1998.827396059946821368141165095480
35 -198.82739605994682136814116509547982
36 21.1726039400531786318588349045201837
37 -0.827396059946821368141165095479816292
38 -0.8273960599468213681411650954798162920
39 -0.82739605994682136814116509547981629200

このようにPythonでは、getcontext().precの値が37以上なら適切な結果が得られます。

Rubyでも、ちょっと工夫すれば適切な結果が得られます(ideone.comで確認)

Windows付属の電卓では、何の工夫も要りません(WIndows 7で確認)。xyのショートカットキーが「Y」なので、「(333.75-77617Y2)*33096Y6+77617Y2*(11*77617Y2*33096Y2-121*33096Y4-2)+5.5*33096Y8+77617/(2*33096)=」と打てば、適切な結果が得られます。

Mac OS Xでも計算できます(10.8.4で確認。できるようになったのは比較的最近のことだと思いますが)。「計算機」で計算する方法はよくわかりませんが、Spotlightに「(333.75-77617^2)*33096^6+77617^2*(11*77617^2*33096^2-121*33096^4-2)+5.5*33096^8+77617/(2*33096)」と入力すれば、適切な結果が得られます。

Windowsの電卓は32桁までの入力にしか対応していないので、230928922463004537535516678003369の平方根を求めたいというようなときに不便ですが、Spotlightなら、sqrt(230928922463004537535516678003369)とすればとりあえず計算できます。

参考:電卓に求められるコト

本稿執筆時点のWolframAlphaでは、まったく違う結果が2つ返ってきて、片方はあっていました

Googleはだめでした

Googleの例は別として、精度を上げてもうまくいかないようにするには、もっと「病的」でなければなりません。

Pythonで階層的クラスタリング

Pythonで階層的クラスタリングをするときは、scipy.cluster.hierarchyを使うのが簡単なのですが、先日ちょっと間違えてしまいまして。

準備

scipyやmatplotlibを使います(インストールは済んでいるとして)。

import scipy
import numpy
import scipy.spatial.distance as distance
from matplotlib.pyplot import show
from scipy.cluster.hierarchy import linkage, dendrogram
from random import random

特徴ベクトルの長さがdimのデータをn個ランダムに作り、実験用のデータとします。

n = 100
dim = 10
data = [[random() for i in range(dim)] for i in range(n)]

方法1

非類似度の指標とクラスタ連結法を指定して、クラスタリングを実行します。

result1 = linkage(data, metric = 'chebyshev', method = 'average')

デンドログラムを描きます。

dendrogram(result1)
show()

このように、データをそのまま使ってクラスタリングするのが簡単です。

方法2

データ間の距離を与えてクラスタリングすることもできます。ただし、データ間の距離は、n行n列の距離行列ではなく、pdist()で作られるベクトルで表現します。

dArray1 = distance.pdist(data, metric = 'chebyshev')
result2 = linkage(dArray1, method = 'average')

assert (result1 == result2).all()#同じ結果

補足

pdist()で作られるベクトルは、次のようなものです。

dArray2 = []
for i in range(n - 1):
  for j in range(i + 1, n):
    dArray2.append(distance.chebyshev(data[i], data[j]));

assert (dArray1 == dArray2).all()#同じもの

このベクトルは、n行n列の距離行列からsquareform()で作ることもできます。

dMatrix1 = numpy.zeros([n, n])
for i in range(n):
  for j in range(n):
    dMatrix1[i, j] = distance.chebyshev(data[i], data[j])

dArray3 = distance.squareform(dMatrix1)
assert (dArray1 == dArray3).all()#同じもの

逆に、pdist()の結果からn行n列の距離行列を作ることもできます。

dMatrix2 = distance.squareform(dArray1)
assert (dMatrix1 == dMatrix2).all()#同じもの

まとめ

  • 階層的クラスタリングは、linkage(data)あるいはlinkage(距離のベクトル表現)で行う。(ちょっとわかりにくい仕様ですね。)
  • 距離のベクトル表現は、pdist(data)あるいはsquareform(n行n列の距離行列)で作れる。
  • n行n列の距離行列は、squareform(距離のベクトル表現)で作れる。

間違い

linkage(n行n列の距離行列)でクラスタリングするのは間違いです。こうすると、行列の各行がクラスタリングの対象データになってしまいます。

しかし、距離行列の各行は、元データのそれぞれが、他のデータとどの程度似ていないかを表現したものなので、これ自体を特徴ベクトルと見なすこともできます。ですから、次のような「間違った」方法も、自覚して使うなら間違いではありません。

result3 = linkage(dMatrix1, metric = 'chebyshev', method = 'average')

さらに、非類似度の指標としてチェビシェフ距離を使うなら、「正しい」方法と同じ結果になります。

assert (result1 == result3).all()#同じ結果

テキストからキーワードを抽出する方法(Yahoo! キーフレーズ抽出API)

テキストからキーワードを抽出しようとするとき、形態素解析して単語の頻度を数えるくらいのことではうまくいかないという話をしました。(テキストからキーワードを抽出する方法(非推奨)

自然言語処理の複雑なアルゴリズムを試すのも勉強にはなるのですが、「今すぐ答えがほしい」という状況を想定して、Yahoo!のキーフレーズ抽出APIを使ってみましょう。

お約束ですが、APIの使い方の基本的なところから学びたいという場合は、拙著『Webアプリケーション構築入門』を参照してください。

準備

Yahoo!のAPIを使うために、アプリケーションIDを登録します。表現は「登録」ですが、実際には「取得」です(参考)。

アプリケーションIDを取得したら、「appid=アプリケーションID」という内容(右辺は適当に置き換える)のファイルyahoo.appidを作っておきます。

練習(APIの使い方の確認)

適当な文章をUTF-8で記述したファイルsentence.txtを用意し、以下のコマンドでAPIを呼び出します。

curl -d @yahoo.appid -d output=json --data-urlencode sentence@sentence.txt\
 http://jlp.yahooapis.jp/KeyphraseService/V1/extract

結果はJSON形式で返ってくるので、それを処理する簡単なスクリプトyahookeywords.pyを用意します。

import sys, json

for line in sys.stdin:
    keywords = json.loads(line)
    for keyword, score in sorted(keywords.items(), key=lambda x:x[1], reverse=True):
        print score, keyword.encode('utf-8')

先のコマンドとパイプでつなげば完成です。

curl -d @yahoo.appid -d output=json --data-urlencode sentence@sentence.txt\
 http://jlp.yahooapis.jp/KeyphraseService/V1/extract |\
 python yahookeywords.py

日本国憲法の前文をsentence.txtに書いておいた場合の結果は以下の通りです。

100 われら
71 諸国民
52 代表者
51 憲法
47 主権
43 自国
43 他国
42 日本国民
41 わが国全土にわ
41 原理
41 恵沢
38 法則
38 隷従
37 詔勅
34 国家
34 惨禍
34 専制
33 いづれ
33 協和
33 理想

本番

日本語の文章がtweets.txtに書かれていると仮定します。ここでは例によって、Streaming APIで取得したつぶやきの処理方法で紹介したもの、つまり「#iwakamiyasumi」というハッシュタグで検索し保存したつぶやきのうち、「年末特番 ジャーナリスト休業直前!上杉隆氏ラストインタビュー」の配信時間中(2011年12月30日15:00から17:45)のもの(本文のみ)を使います。

Yahoo! キーフレーズ抽出APIは、1回のリクエストのサイズが100KBまで(URLエンコードした状態で?)に制限されているので、テキストから「( #iwakamiyasumi live at http:…)」と「http…」という文字列を削除しましょう。

cat tweets.txt | sed -e "s/(.*live.at.*)//g" | sed -e "s/http.*[:space]//g" > tmp

補足:Ustream上でつぶやかれたものだけをとりだすなら、「( #iwakamiyasumi live at http:…)」が含まれるものだけを残してください。

方針1:ランダムサンプリング

ここで利用するテキストは、上述の準備をした後でもサイズ制限を超えているので、小さくなるようにランダムサンプリングをしてからAPIを呼び出してみましょう(「3/13」がサンプリングレートです。この値は適当に調整してください)。

cat tmp | awk '{ if (rand() < 3/13) print $0; }' |\
curl -d @yahoo.appid -d output=json --data-urlencode sentence@-\
 http://jlp.yahooapis.jp/KeyphraseService/V1/extract |\
 python yahookeywords.py

結果は以下のようになり、以前の結果と比べればずいぶんよくなっています(ランダムサンプリングなので、結果は実行ごとに変わります)。

100 上杉隆
99 岩上
66 uesugitakashi
61 iwakamiyasumi
46 IWJ・UST
46 上杉隆氏ファイナルインタビュー
43 ジャーナリスト休業直前
41 IWJ_UESUGI
38 ジャーナリズム
38 kfuruta777
36 ch1
35 T7OSEDyz
35 インターン
34 journalist
33 岩上安身
32 ホロスコープネイタル
32 自由報道協会
31 Toraji Tachiki
31 プレゼンテーター
31 佐々木俊尚

方針2:テキストの分割

テキストを分割し、APIを複数回呼び出すことも考えられます(実行前にresult.txtやx*を削除してください)。

split --line-bytes 30k tmp

for file in x*
do
  curl -d @yahoo.appid -d output=json --data-urlencode sentence@$file\
   http://jlp.yahooapis.jp/KeyphraseService/V1/extract |\
   python yahookeywords.py >> result.txt
done

cut -d ' ' -f 2 result.txt | sort | uniq

これによって得られるキーフレーズは以下のようになります(APIが変わらなければ、結果はいつも同じになります)。

DEMA, GGB9500, IWJ, IWJ1ch, IWJ_UESUGI, IWJ・UST, Journalist, NHKスペシャル, P8B88dXY, T7OSEDyz, Toraji, U-st, Ust, Web, asd59, ch1, genpatsu, higahayato, iwakamiyasumi, iwakamiyasumi&quot, iwakamiyasumi2, iwakamiyasumi3, iwj_uesugi, kfuruta777, kinoryuichi, komatsunotsuma, ky00950, kyokonoguchi, pkashima, uesugitakashi, w, おしどりマコ, つねつぐ氏, アクセス権, アワード, インターン, インタビュー, ウギリークス, ガサ入れ, ガンバッテ, グリニッジ, ゴルフジャーナリスト, ジャーナリスト, ジャーナリスト休業直前, ジャーナリズム, デマYAROU, デマYallo, デマYaroo, デマyallo, デマ野郎, ニューヨークタイムズ, ハイパーメディアキュリエーター, ヒマナイヌ川井, ピューリッツアー賞, ピューリッツア賞もの, ピュリッツァー, プチ鹿島, プルト君, プレゼンテーター, ホロスコープネイタル, マイク, メディア, メルマガハ, 安川@shinfonic, 炎上ビジネス, 可視化, 岩上, 岩上さん上杉, 岩上安身, 記者クラブ, 恒次, 黒い池上彰, 今西, 佐々木俊尚, 佐藤栄作久, 自由報道協会, 社団法人, 小沢, 小渕, 上杉, 上杉リークス, 上杉隆, 上杉隆ファイナルインタビュー, 上杉隆氏ファイナルインタビュー, 上杉隆氏ラストインタビュー, 神々しい岩神, 世川行介, 孫崎, 孫碕, 大批判, 池田信夫, 朝生, 田中真紀子, 読売ツネツグ, 読売新聞, 日隅, 畠山, 鳩山, 鳩山ゆきよ, 鳩山由紀夫, 福島県民, 立花隆, 鈴木宗男

先の結果よりは冗長ですが、もともとの映像がどういうものだったのか、だいたいわかるようなものになっています。

テキストからキーワードを抽出する方法(非推奨)

Streaming APIで大量のつぶやきをリアルタイムに保存する方法(cURL編)で述べたように、Ustreamなどで重要な映像が配信されるときには、Twitterなどでその内容をtsudaってくれる人がいます。そうやって生み出される大量のテキストが、映像のメタ情報としてもっと活用されるとうれしい、という話です。

最初に試したいのはキーワードの抽出です。

テキストファイルからキーワードを取り出そうとするとき、多くの学生はまず「形態素解析」を試みます。自然言語処理についてちゃんと学びたいときは、こういうところからじっくり勉強するといいのでしょうが、単に形態素解析するだけではあまりいい結果は得られません。

実際にやってみましょう。

4873114705形態素解析システムの使い方は、『入門 自然言語処理』(オライリー, 2010)などで紹介されていますが、環境によってはもう少し簡単です。たとえば、Ubuntuの場合、有名な日本語形態素解析システムMeCabをPythonで使うための準備は、次のようにとても簡単です。

sudo apt-get install python-mecab mecab-jumandic-utf8

インストールが終わったら、Pythonのインタラクティブシェルで動作を確認します。

import MeCab
mecab = MeCab.Tagger()
sentence = "すもももももももものうち"
print mecab.parse(sentence)

実行結果は次の通りです。

す	接頭辞,名詞接頭辞,*,*,す,す,*
もも	名詞,普通名詞,*,*,もも,もも,代表表記:股
も	助詞,副助詞,*,*,も,も,*
もも	名詞,普通名詞,*,*,もも,もも,代表表記:股
も	助詞,副助詞,*,*,も,も,*
もも	名詞,普通名詞,*,*,もも,もも,代表表記:股
の	助詞,接続助詞,*,*,の,の,*
うち	名詞,副詞的名詞,*,*,うち,うち,*
EOS

というわけで、うまくいってませんが先に進みましょう。形態素ごとに何らかの処理を行う練習です。

node = mecab.parseToNode(sentence)
node = node.next
while node:
    print node.surface, node.feature
    node = node.next

実行結果は先の場合とほとんど同じになります。形態素がnode.surfaceに、その素性がnode.featureに格納されます。

PythonでMeCabを使う方法がわかったので、形態素の出現回数を数えてみましょう。出現回数た多い単語がキーワードであろう、という発想です。対象テキストは、Streaming APIで取得したつぶやきの処理方法で紹介したもの、つまり「#iwakamiyasumi」というハッシュタグで検索し保存したつぶやきのうち、「年末特番 ジャーナリスト休業直前!上杉隆氏ラストインタビュー」の配信時間中(2011年12月30日15:00から17:45)のものです(本文のみ)。そのテキストが、ファイルtweets.txtに格納されていると仮定します。

次のようなコード(wordcounter.py)は、名詞と見なされたものの出現回数を数え、大きいものから順に出力します(ASCII数字のみからなる単語は除く)。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import MeCab, sys

mecab = MeCab.Tagger()
counter = {}

for line in sys.stdin:
    node = mecab.parseToNode(line)
    node = node.next
    while node:
        if node.feature.startswith("名詞,"):
            if not node.surface.isdigit():
                counter[node.surface] = counter.get(node.surface, 0) + 1
        node = node.next

for surface, count in sorted(counter.items(), key=lambda x:x[1], reverse=True):
    print count, surface

cat tweets.txt | python wordcounter.py | head -n 20」などとして、出現回数が多いものから順に20個抽出すると次のようになります。


1509 iwakamiyasumi
1433 t
1406 co
1406 http
1301 live
1301 at
533 上杉
261 隆
157 岩上
121 記者
112 福島
106 ジャーナリスト
81 IWJ
73 クラブ
70 報道
64 uesugitakashi
63 こと
62 w
57 bCljBsCQ
54 ー

というわけで、あまりうまくいっていませんが、形態素解析システムの使い方がわかったので、とりあえずこれでよしとしましょう。

もう少し楽な方法を後で紹介します。

テキストからキーワードを抽出する方法(Yahoo! キーフレーズ抽出API)