かな配列を自分のSlackの発言から作る
このエントリーはKMC Advent Calendar 2018の14日目の記事です。
13日目の記事は以下の記事でした。
こんにちは。4回生になり、卒論の進捗に追われているwass80です。
キーボードの自作が流行っていますね。 自作は大変なので、今回は今あるキーボードで早く入力する方法を考えます。
tl;dr
- とりあえず手でかな配列を作って試す。
- 自分のSlackの発言から1-gram, 2-gramを求める。
- 自分にとって押しやすいかな配列を焼きなましで求める。
かな入力
ローマ字での入力ではなく、50音での入力です。 多くのキーボードに書かれている「JISかな」による入力が最も普及しています。 ローマ字入力では多くが1文字2打鍵(ha, ki)ですが、 かな入力では多くは1文字1打鍵(は, き)なので有利と考えられます。
しかし、キーボードに書かれている「JISかな」は、あまり最適化されていないように感じます。 なので、この配列をいじることで、より楽なかな入力について考えます。
かな配列の種類
いろいろなカナ入力配列 様々なかな配列についてはここが詳しいです。
- 用いるキーの数と配列
- シフトキーの数と種類
によって様々配列が提案されています。
3段配列、4段配列
使うキーの数は主に段数で示されます。 手元の「JISかな」を見るとわかるように、 「JISかな」は上から1段目数字のキーも使って全部で4段のキーを使っています。
有名な「親指シフト」は3段配列として知られています。
By udfoto - 自室にて撮影, CC 表示-継承 3.0, Link
更に極端な例だと2段配列や1段配列があります。 しかしこの場合、使えるキーが20個程度かより少ない数になります。 原則1キーに1かなを対応させる、かな配列の入力をするのには困難性が伴います。
シフトキー
たとえ4段配列にして、すべてのキーをかなに対応させても、キーは足りません。 入力したい文字は84ほどあります。
ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんー
そのため、配列のモードを切り替え、別の配列を入力する方法が必要になります。
前シフト、後シフト
入力したい文字の前(または後)にシフトキーを押すことで、別の文字を入力します。 お手元の「JISかな」では「゛」「゜」のキーが後シフトです。
「か」+「゛」=「が」
となります。
同時シフト
同時シフトは英字キーボードでのシフトキーと同じものです。 シフトキーが押されている間、別の文字が入力されます。
左右シフト
シフトキーが1つでは足りない場合は、シフトキーに左右で別の役割をもたせます。 「親指シフト」ではキーを押した時、同じ側のシフトか、反対側のシフトかで別の文字が入力されます。
とりあえず手でぽちぽち作った配列
かなの頻度表(=1gram)を見ながら、とりあえず手で配列を作りました。 参考: 漢直ノート ひらがな1-gram表
仕様
- Shiftキーは英数入力のためになるべく用いない。
- 濁点キーを前入力する。
- ゛+た=だ
- 濁点キーにより大小も反転する。
- ゛+っ=つ
- 二文字書かれているキーはその2文字が清濁の関係とみなす。
- ゛+め=ぬ
- Shift ゛+ は = ぱ
- 1+せ=8
考えたこと
- 頻度の高い文字を押しやすい上から3段目におく。
- 濁点キー「゛」が右手側に1つしかないため、清音を左手側に集めている。
- 左右のキーを交互に用いると速いため。
- 実は「JISかな」でもそのようになっている。
- 「が」は高頻度なので、単体でキーが存在する。
- 「っ」「ゃ」「ゅ」「ょ」が表の配列で小文字。
- 大文字より小文字のほうが頻度が高いため。
- あまりに使わない文字を清音の裏(濁点キーで入力)に押し込んだ。
試す
google IMEのローマ字テーブルを作りました。
問題点
- 配列を覚えるのが大変です。
- あまり頻度順に並んでいないように感じました。
- シフトキーは両側にないとキーの配列の左右の自由度が失われます。
真面目に最適化を考える
頻度順に並んでいないように感じる理由は、参考にした1-gramが自分の入力したコーパスではないからだと考えられます。 なので、サークルのSlackから自分の発言を抜き出し、自分のコーパスを作ります。 また、2-gram(連続2文字の頻度)を参考にして、 なるべく左右の手が交互に使われる配列を目指します。
python notebookはこちらです。 Keyboard配列 · GitHub
前処理
Slackから発言を取得する
slackclientを使いました。
コード
from slackclient import SlackClient slack_token = TOKEN sc = SlackClient(slack_token) def fetch_msg(page = 1, count = 100): try: res = sc.api_call( "search.all", query = "from:@wass80", count = count, highlight = False, page = page ) except e: print("Retry") sleep(10) yield from fetch(page, count) if "messages" not in res: print("missing", res) return res = res["messages"]["matches"] for r in res: yield r["text"] def fetch_all(count = 3, batch = 100): for i in range(1, count + 1): print("Fetch: %d" % i) yield from fetch_msg(i, batch) sleep(10) with open("msg.txt", "w") as f: for m in fetch_all(170): f.write(json.dumps(m) + "\n")
発言から記号を取り除く
def remove_symbols(s): s = re.sub(re.compile(r"```.+```", re.MULTILINE | re.DOTALL), " ", s) s = re.sub(r"<[^>]+>", " ", s) s = re.sub(r"`[^`]+`", " ", s) s = re.sub(r":[^:]+:", " ", s) return s remove_symbols("hoge<piyo>hoge`piyo`hoge```\npiyo\n```\nhoge\n:piyo:")
'hoge hoge hoge \nhoge\n '
読み方からひらがなに戻す
janomeを使いました。
from janome.tokenizer import Tokenizer tk = Tokenizer() def reading(s): res = "" for t in tk.tokenize(s): if t.reading != "*": res += t.reading else: res += " " return res reading("丸竹夷に押御池")
'マルタケエビスニ '
辞書にない単語は無視されますが、あまり問題にはならないでしょう。
1-gram, 2-gramを求める
1-gram
せっかくなので、手で作ったときに参考にした1-gramと比較しました。 文字が自分の1-gramです。丸が参考にした1-gramです。 青は参考より自分の頻度が多い。赤は逆です。
2-gram
これは、次の項目の1重キー、2重キーの計算結果が反映されています。
配列計算
配列の定義
- 1重キー(清音のみ)と2重キー(清濁)を定義します。
- 前シフトキー「゛」の後、2重キーは裏の文字になります。
- シフトキー「゛」の場所は固定。両側に配置。
2重キーの分解
逆に、表も裏もよく使うキーの場合、分解します。 「かが」→「か」「が」
2重キーの反転
裏のほうがよく使うキーの場合、反転します。 「ゆゅ」→「ゅゆ」
結果
1重キー(頻度順)
'イ', 'ン', 'ウ', 'カ', 'シ', 'ノ', 'ナ', 'タ', 'デ', 'テ', 'ル', 'マ', 'ニ', 'ガ', 'コ', 'ジ', 'リ', 'ョ', 'ア', 'ダ', 'ラ', 'オ', 'モ', 'レ', 'ヨ', 'エ', 'ヲ', 'メ', 'ミ', 'ワ', 'ム', 'ネ', 'ロ', 'ポ', 'プ', 'パ', 'ヅ', 'ペ', 'ピ', 'ヌ',
2重キー
[('キ', 'ギ'), ('ク', 'グ'), ('ケ', 'ゲ'), ('サ', 'ザ'), ('ス', 'ズ'), ('セ', 'ゼ'), ('ソ', 'ゾ'), ('チ', 'ヂ'), ('ッ', 'ツ'), ('ト', 'ド'), ('ハ', 'バ'), ('ヒ', 'ビ'), ('ブ', 'フ'), ('ヘ', 'ペ'), ('ホ', 'ボ'), ('ヤ', 'ャ'), ('ュ', 'ユ'), ('ー', '〜')]
1重キーの圧縮
1重キーと2重キーの合計がキーの数より多いので、 1重キーの裏に1重キーを置くことで2重キーにします。 これによって「゛」+「シ」=「ワ」など、多少理不尽なことが起こりますが、諦めます。
配列の計算
- キーの場所をどこにするか。
- 圧縮された1重キーの場合、裏に回ったキーの場所をどこにするか。 この2つがパラメータとなります。
配列のコスト
3種類のパラメーターをキーの場所ごとに定義しました。
self.press = [100, 10, 0, 20][y] + [1, 0, 0, 0, 1, 1, 0, 0, 0, 10, 20, 30][x] self.finger = [1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 8, 8][x] self.hand = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1][x]
- pressは押しにくさを表す重み。
- fingerは押す指。
- handは押す手。
最終的な配列のコストは:
- 各キーのpressとその1_gramから次の値を合計する。
- front = 1_gram * press * 1gram_w (表キーの押しにくさ)
- back = 1_gram * press * 1gram_w * back_w (裏キーの押しにくさ)
- 2_gramから次の値を合計する。
- 同じ指を連続で使うなら finger = 2_gram * same_finger_w
- 同じ手を連続で使うなら hand = 2_gram * same_hand_w
それぞれ重み(*_w)
は何度か焼きなましして適当に決定しました。
焼きなまし
適当に生成した配列にこの2つの演算を適用します。 上で定義した配列のコストが小さくなるように焼きなましをします。
初期生成
上が表、下が裏です。
ー | ュ | ヤ | ホ | ヘ | ブ | ヒ | ハ | ト | ッ | チ | ソ |
セ | ス | サ | ケ | ク | キ | イ | ン | ウ | カ | シ | ノ |
゛ | ナ | タ | デ | テ | ル | マ | ニ | ゛ | @ | ガ | コ |
ジ | リ | ョ | ア | ダ | ラ | オ | モ | レ | ヨ | エ | ヲ |
〜 | ユ | ャ | ボ | ペ | フ | ビ | バ | ド | ツ | ヂ | ゾ |
ゼ | ズ | ザ | ゲ | グ | ギ | メ | ミ | ワ | ム | ネ | ロ |
ポ | プ | パ | ヅ | ペ | ピ | ヌ | - | - | - | - | - |
- | - | - | - | - | - | - | - | - | - | - | - |
- コスト: 21.629534099116793,
- 'hand': 0.39129205368876097,
- 'finger': 0.4639484594384564,
- 'front': 18.760037452391433,
- 'back': 2.0142561335981406
焼きなまし後
エ | ソ | ー | レ | セ | ブ | ヤ | ュ | ヲ | ホ | ヒ | ヘ |
デ | ス | ク | ル | ッ | テ | タ | ハ | ナ | コ | モ | チ |
゛ | ン | イ | シ | ウ | ノ | ト | カ | ゛ | @ | ジ | ア |
ラ | リ | キ | ニ | オ | ョ | マ | ガ | ダ | サ | ヨ | ケ |
- | ゾ | 〜 | - | ゼ | フ | ャ | ユ | - | ボ | ビ | ペ |
ヅ | ズ | グ | ペ | ツ | ヌ | プ | バ | パ | - | - | ヂ |
ポ | ミ | メ | ワ | ム | ロ | ド | ネ | - | - | - | - |
- | ピ | ギ | - | - | - | - | - | - | ザ | - | ゲ |
- コスト: 15.252237067009535,
- 'hand': 0.3088283757887919,
- 'finger': 0.20433342691029233,
- 'front': 13.840631230449489,
- 'back': 0.8984440338609608
できた配列を試す
- Google IMEのローマ字テーブルとして出力して試しました。
- わかったこととして、4行配列は手の動きが大きすぎてやはり面倒です……。
- 指のもつれとかは起こりにくいのかもしれないです。
- ただ、キーを全然覚えられる気がしなかったので諦めてしまいました。
phoenix配列
やはり4段配列は手を1段目に持っていくのが面倒です。 手が動かない配列はないかと探すと、phoenix配列が良さそうでした。
phoenix配列は2段配列のアルファベット方式です。 サイトにはgoogle IME用のテーブルが用意されているので、手軽に試すことが出来ます。
phoenix Real Time Input Method
漢字直接入力も対応しているらしいですが、流石に覚えられない自信があります。
wass-phoenix配列
せっかく自分の1-gramを求めたので、この配列も自分に最適化しました。
結局配列を作っても、それを覚えるコストが高すぎて、全然使いこなせません……。 なにかいい方法はあるのでしょうか。