Seq2Seqを使った英日翻訳機
RNN(Recurrent Neural Network)の実例の一つに翻訳機があります。 今回はkerasのRecurrentレイヤーを使い、Seq2Seq(Encoder-Decoder)モデルの英日翻訳機を実装してみます。
データセット
Kerasのexampleでは文字単位での英仏翻訳が行われていますが、今回は英日の翻訳なので田中コーパス1をデータセットとして使い、単語レベルでの翻訳を行います。
1.田中コーパスのダウンロード
$ wget ftp://ftp.monash.edu/pub/nihongo/examples.utf.gz $ gunzip examples.utf.gz
2.整形
田中コーパスのデータは以下のようになっています。
$ head examples.utf A: ムーリエルは20歳になりました。 Muiriel is 20 now.#ID=1282_4707 B: は 二十歳(はたち){20歳} になる[01]{になりました} A: すぐに戻ります。 I will be back soon.#ID=1284_4709 B: 直ぐに{すぐに} 戻る{戻ります} A: すぐに諦めて昼寝をするかも知れない。 I may give up soon and just nap instead.#ID=1300_4727 B: 直ぐに{すぐに} 諦める{諦めて} 昼寝 を 為る(する){する} かも知れない A: 愛してる。 I love you.#ID=1434_4851 B: 愛する{愛してる} A: ログアウトするんじゃなかったよ。 I shouldn't have logged off.#ID=1442_4858 B: ログアウト~ 為る(する){する} の{ん} だ{じゃなかった} よ[01]
このファイルに対し、1) Aの行を抜き出し 2) #以下を取り除いて 3) tabで区切り 4) それぞれ日本語(教師データ)・英語(学習データ)ファイルとして保存します。
(日本語の場合は一文字をデータの最小単位とするため、半角スペースを間にいれました。)
文章のベクトル化
入力である文章はそのままではモデルへの入力として使う事ができません。keras.preprocessing.text.Tokenizerクラスにより文章をベクトルに直します。 (文章の先頭と末尾には系列の先頭・終了を表すタグをつけておきます。)
from keras.preprocessing.text import Tokenizer def load_dataset(file_path): tokenizer = Tokenizer(filters="") texts = [] for line in open(file_path, 'r'): texts.append("<s> " + line.strip() + " </s>") tokenizer.fit_on_texts(texts) return tokenizer.texts_to_sequences(texts), tokenizer train_X, tokenizer_e = load_dataset('tanaka_corpus_e.txt') train_Y, tokenizer_j = load_dataset('tanaka_corpus_j.txt')
これにより、
<s> Muiriel is 20 now. </s>
→[1, 16504, 7, 1851, 170, 2]
<s> ム ー リ エ ル は 2 0 歳 に な り ま し た 。 </s>
→[2, 142, 38, 93, 328, 71, 4, 134, 106, 505, 8, 9, 33, 21, 11, 7, 1, 3]
と表現することができました。
モデル
学習時
Encoderの入力にtrain_Xデータ、Decoderの入力にtrain_Yデータを使い、教師データとしては入力として用いたtrain_Yの一時刻先のデータを使います。
from keras.models import Model from keras.layers import Input, Embedding, Dense, LSTM emb_dim = 256 hid_dim = 256 ## エンコーダ encoder_inputs = Input(shape=(seqX_len,)) # Embed処理: https://keras.io/ja/layers/embeddings/ encoder_embedded = Embedding(word_num_e, emb_dim, mask_zero=True)(encoder_inputs) encoder = LSTM(hid_dim, return_state=True) # 内部状態を返すよう、return_state=Trueとしておく _, state_h, state_c = encoder(encoder_embedded) # outputsは捨てる ## デコーダ decoder_inputs = Input(shape=(seqY_len,)) decoder_embedding = Embedding(word_num_j, emb_dim) decoder_embedded = decoder_embedding(decoder_inputs) decoder = LSTM(hid_dim, return_sequences=True, return_state=True) # 一入力ごとの出力を得るため、return_sequences=Trueとしておく # initial_stateにencoder_statesを与えることでエンコーダとデコーダを繋ぐ decoder_outputs, _, _ = decoder(decoder_embedded, initial_state=encoder_states) decoder_dense = Dense(word_num_j, activation='softmax') decoder_outputs = decoder_dense(decoder_outputs) model = Model([encoder_inputs, decoder_inputs], decoder_outputs) model.compile(optimizer='rmsprop', loss='sparse_categorical_crossentropy')
学習
## 教師データの用意 # 1. 各データ先頭を捨てる next_inputs = train_Y[:, 1:] # 2. 末尾を0でパディングする decoder_target_data = np.hstack((next_inputs, np.zeros((len(train_Y),1), dtype=np.int32))) # 3. 出力は(系列の数, 入力の数, 単語のベクトル)の3次元なので一次元上げる decoder_target_data = np.expand_dims(decoder_target_data, -1) model.fit([train_X, train_Y], decoder_target_data, batch_size=128, epochs=15, verbose=2, validation_split=0.2)
予測時
予測時はt-1時点でのデコーダの出力がtの入力となるため、RNNのループを手で回します。
モデル
# エンコーダ:最終的な状態を返す encoder_model = Model(encoder_inputs, encoder_states) decoder_state_input_h = Input(shape=hid_dim,) decoder_state_input_c = Input(shape=hid_dim,) decoder_state_inputs = [decoder_state_input_h, decoder_state_input_c] decoder_inputs = Input(shape=(1,)) decoder_embedded = decoder_embedding(decoder_inputs) decoder_outputs, state_h, state_c = decoder( decoder_embedded, initial_state=decoder_state_inputs ) decoder_states = [state_h, state_c] decoder_outputs = decoder_dense(decoder_outputs) # デコーダ decoder_model = Model( [decoder_inputs] + decoder_state_inputs, # t-1での出力・状態を受け取る [decoder_outputs] + decoder_states )
出力
def decode_sequence(input_seq): # エンコーダから最終的な状態を得る states_value = encoder_model.predict(input_seq) bos_eos = tokenizer_j.texts_to_sequences(["<s>", "</s>"]) # 最初の入力として先頭文字<s>を与える target_seq = np.array(bos_eos[0]) output_seq = bos_eos[0] # ループを回す while True: # 前回の出力と状態ベクトルで予測 output_tokens, h, c = decoder_model.predict( [target_seq] + states_value ) sampled_token_index = [np.argmax(output_tokens[0, -1, :])] output_seq += sampled_token_index if (sampled_token_index == bos_eos[1] or len(output_seq) > 1000): break # 入力・状態の更新 target_seq = np.array(sampled_token_index) states_value = [h, c] return output_seq detokenizer_e = dict(map(reversed, tokenizer_e.word_index.items())) detokenizer_j = dict(map(reversed, tokenizer_j.word_index.items())) input_seq = pad_sequences([test_X[0]], seqX_len, padding='post') print(' '.join([detokenizer_e[i] for i in test_X[0]])) # 英文 print(' '.join([detokenizer_j[i] for i in decode_sequence(input_seq)])) # 予測 print(' '.join([detokenizer_j[i] for i in test_Y[0]])) # 正解
結果
データセットを50,000にし、5エポック回してみます。
<s> he never makes a show of his learning. </s> <s> 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は 彼 は <s> 彼 は 決 し て 自 分 の 学 識 を 見 せ び ら か せ な い 。 </s>
うーん。。。
コード全体
Next Step
- Attentionモデルの導入
- 日本語を正しくトークナイズ
- 入力を逆から与えてみる
- 日本語と英語だと語順が大きく違うのでうまくいくか疑問
Ref
kerasを使ったreuter記事分類のexampleをなぞる
今回はその中からReuter記事データの分類をしてみます。 基本的にはこちらのexampleに示されているコードをなぞる形です。
kerasのインストール
pythonは3系を使います。
TensorFlowのインストール
keras自身のインストールに先立ち、バックエンドとして使用する機械学習ライブラリをインストールする必要があります。TensorFlowやTheano,CNTKなどから選ぶ事ができますが2、今回はTensorFlowを利用します。
$ sudo apt-get install python3-pip python3-dev $ sudo pip3 install tensorflow
kerasのインストール
$ sudo pip3 install keras
Reuter記事の分類
データセットのロード
from keras.datasets import reuters (x_train, y_train), (x_test, y_test) = reuters.load_data(num_words=1000, test_split=0.2)
記事のデータセットはエンコードされ、配列になっています。それぞれの単語にはデータセット全体の中で頻度の多い方から順にインデックス番号が振られています。
x_train.shape # (8982,) x_train[0][:10] # [1, 4, 2, 2, 9, 697, 2, 111, 8, 25]
引数のnum_words
は最頻出上位○○位の値、test_split
はtrainデータとtestデータの割合(今回は4:1)を指定しています。
データの前処理
入力データ(x)は配列から行列の表現に変換する必要があります。
from keras.preprocessing.text import Tokenizer tokenizer = Tokenizer(num_words=1000) x_train = tokenizer.sequences_to_matrix(x_train, mode='binary') x_test = tokenizer.sequences_to_matrix(x_test, mode='binary')
tokenizer
のイニシャライザには先ほどと同じ値を指定します。sequences_to_matrix
のmode
にbinaryを指定すると、文中にインデックス番号の単語が含まれていれば1、そうでなければ0が値となるベクトルに変換されます。
教師データ(y)はone-hot表現に変換します。
from keras.utils import to_categorical y_train = to_categorical(y_train) y_test = to_categorical(y_test)
モデルの構築
exampleよりもシンプルなモデルにします。出力のサイズ(分類の数)は46です。
from keras.models import Sequential from keras.layers import Dense model = Sequential() model.add(Dense(512, input_shape=(1000,), activation='relu')) model.add(Dense(256, activation='relu')) model.add(Dense(128, activation='relu')) model.add(Dense(46, activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
学習
model.fit(x_train, y_train, batch_size=32, epochs=5, verbose=2, validation_data=(x_test, y_test))
(メモ: GCEのn1-standard-1タイプインスタンスで各エポックあたり6s)
評価
score = model.evaluate(x_test, y_test, verbose=0) score[0] # 損失関数の値: 1.0874391948125879 score[1] # 正答率: 0.7738201247191493
コード全体 → reuter_mnist.py · GitHub
疑問
- modeの値にbinary以外を指定するとどうなるんだろう...
Refs
publickeyを使ったsshの設定
1.鍵を用意する
$ ssh-keygen -t rsa
2.公開鍵をインスタンス上の ~/.ssh/authorized_keys
に追記する
$ cat ~/.ssh/ssh_test.pub | ssh ([USER]@)[HOSTNAME] "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys"
$ ssh -i ~/.ssh/ssh_test ([USER]@)[HOSTNAME]
4.~/.ssh/config
を設定する
Host ssh-test HostName [HOSTNAME] User [USER] IdentityFile /path/to/home/.ssh/ssh_test
$ ssh ssh-test
Refs
Solrについて
Solrとは
Solr is the popular, blazing-fast, open source enterprise search platform built on Apache Lucene™.
- オープンソースの全文検索エンジン
- 検索プログラムのLuceneがベース
- Standaloneモードとクラウド運用に特化したSolrCloudモードがある
- データセットはcore(standalone)またはcollection(SolrCloud)と呼ばれる
- データをインポートする際スキーマは指定しなくても自動で解析してくれる
- 転置インデックスという方式でインデックスを作成する
- トークナイズの方法には形態素解析やN-gramがある
- SQLのGROUP BYのような検索をする、ファセット機能をもつ
- 地理空間検索ができる
こちらの記事が要点を抑えていてわかりやすいです。
文章を単語ごとに分解する方法。
例:「東京特許許可局」 →「東京」「特許」「許可局」
意味のわからないトークンが作られない一方、例えば「京特」で調べた時に引っかからないという事が起こる。
文章をN文字で機械的に分解する方法。
例:「東京特許許可局」N=2 →「東京」「京特」「特許」「許許」「許可」「可局」
検索する文字に対して漏れなく結果を返せる反面、それが全く関連のないノイズとなりえる。
データソースとトークンの間のM:N関係。
動かしてみる
こちらの記事を参考にdockerで動かしてみます。
$ docker pull solr $ docker run -d --name=solr -p8983:8983 solr
NOTE: docker run
をすると8983ポートでsolrが起動します。もし以下のTutorialのようにSolrCloudを試したい場合は違うポートを繋いでおき、コンテナ内で別プロセスを立ち上げるのが楽です。またその後任意のコマンドを実行する際にはポートを指定する必要があるので注意。
疑問
- Schemaの書き方
- 検索の適合度
- 地理検索機能の実例
Refs
読書リスト2017
あっと言う間すらないほど早い一年でした。2017年版です。
- チームが機能するとはどういうことか
- Webを支える技術
- 大規模開発サービス技術入門
- Webサービスの作り方
- まつもとゆきひろ 言語の仕組み
- ゼロから作るDeep Learning
- Server-Side Swift
- きつねさんでもわかるLLVM
- 開眼!JavaScript
- JavaScript本格入門
- Reactビギナーズガイド
- ふつうのLINUX
- 新しいLinuxの教科書
- プロになるためのWeb技術入門
- できるPRO Apache Webサーバー
- Real world http
- 計算理論の基礎
- オペレーションシステムの仕組み
- JavaScriptデザインパターン
- 最短経路の本
- アルゴリズムサイエンス 出口からの超入門
- たった2日でわかるLinux
- PHPはどのように動くのか
- プログラミングコンテストチャレンジブック
- アルゴリズムクイックリファレンス
- 達人に学ぶSQL徹底指南書
- 達人に学ぶDB設計徹底指南書
- The Go Programming Language
- SQLパズル
- プログラマのためのSQL
- SQLアンチパターン
計31冊(減ってるやん。。。)
前半は相変わらず乱読を続けていましたが、秋頃から一つのテーマを掘り下げるスタイルにしました。 こちらの方がいろんな知識を線で捉えられるため体系的に学びやすいと感じているのでとりあえず続けてみようと思います。
春から夏にかけてJavaScriptに興味が湧いたため手をつけていたのですが、仕事で使わないだけでなくプライベートで何かを作る、ということもしなかったため中途半端な知識になってしまったのが残念です。反省。
今は「トレンドを追うよりもコンピュータサイエンスに近いところを学んで足腰を鍛えることを優先した方が後々効率が良いのではないか」という気がしているので、以前よりもだいぶ慎重に読む本を選んでいます。
昨年「2017年学びたいこと」として
をあげていました。達成度は30%以下です。反省。
来年は
- デザインパターン
- OS、言語処理系
- デザイン
あたりを中心に読みたいなと思っています。
また、最近こちらのエントリを読んで感心しました。
「より効率的な読書法」も同時に求めていきたいと思います。
Varnishについて
今週FastlyのStockholm(BMA)で障害が発生1し、私たちのサイトも影響を受けました。その際出てきた「varnish」というミドルウェアについて調べたときのメモです。
Varnishとは
Varnish Cache is a web application accelerator also known as a caching HTTP reverse proxy.
- HTTPレベルでキャッシュを行うHTTPアクセラレータ。
- カーネルの機能を最大限利用することで高速化を図っている。
- ディスクへのデータ書き込みをデフォルトでは一切しないので、プロセス終了時に全てのキャッシュが消える。
- 設定はVCL(Varnish Configuration Language)というDSLで記述する。動的に変更可能。
- ログもデフォルトではファイルに書き込まれない。
- Fastlyの中心技術2。各リージョンにVarnishキャッシュサーバを置いていると雑に考えても間違いではなさそう。
- SSLが使えないのでnginxを前に置く構成が多いらしい。
動かしてみる
varnishをnginxの手前に置いて、nginxのindex.htmlをvarnish経由で返してみる。
参考記事: Getting Started with Varnish Cache
環境
1. コンテナの起動 3
$ docker run --privileged -d -p80:80 --name=varnish centos /sbin/init
2. nginxのインストール&起動 4
$ sudo vi /etc/yum.repos.d/nginx.repo
[nginx] name=nginx repo baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/ gpgcheck=0 enabled=1
$ yum install -y nginx $ systemctl start nginx
3. Varnish Cashのインストール
$ yum install -y epel-release $ yum install -y varnish
4. nginxのポートを8080に変更
etc/nginx/conf.d/default.conf
server { listen 8080; ... }
リロード
$ systemctl reload nginx
5. varnishのポートを80に変更
etc/varnish/varnish.params
VARNISH_LISTEN_PORT=80
起動
$ systemctl start varnish
6. アクセスしてみる
$ curl -I http://localhost HTTP/1.1 200 OK Server: nginx/1.13.6 Date: Sun, 19 Nov 2017 10:46:05 GMT Content-Type: text/html Content-Length: 612 Last-Modified: Tue, 10 Oct 2017 15:59:40 GMT ETag: "59dcee6c-264" X-Varnish: 22 20 Age: 3 Via: 1.1 varnish-v4 Connection: keep-alive
2回目アクセス以降X-Varnish
の値が2つ返っていればキャッシュされている5。
また、nginxのアクセスログに2回目以降のアクセスログが残らないことも確認できた。(キャッシュ間隔のデフォルトは5分。)
疑問
- (リバースプロキシとしている)nginxでも同じことができるのではないか。
- わざわざnginxの後ろにVarnishを置く理由。
参考
JSON-RPCについて
JSON-RPCについて調べた際のメモです。
特徴
JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol.
- データのエンコードにJSONを利用したRPC(Remote Procedure Call)1プロトコルの一種。
- レスポンスを期待しない「通知」、複数の呼び出しを一度に行う「Batch」が可能。
- リクエスト、レスポンスのjsonに含めるべきフィールドが決められており(プロトコルなので)、それを満たしさえすればよい。
- HTTPメソッドは全てPOSTとするのが通例。
リクエスト
以下、例は主にJSON-RPC 2.0 Specificationから拝借いたしました。
- jsonrpc [string]: JSON-RPCのバージョン(現在2.0)
- method [string]: 呼び出したいメソッド名
- params [array|object]: メソッドの引数(単純に列挙する場合はarray、ラベルを使う場合はobject)
- id [string|int]: リクエストを識別するID、レスポンスに同じ値が含まれる、よってBatch(後述)で送ってもどのリクエストに対するレスポンスかを識別できる
{ "jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1 }
ラベル付き
{ "jsonrpc": "2.0", "method": "subtract", "params": { "subtrahend": 23, "minuend": 42 }, "id": 3 }
レスポンス
成功時:
失敗時:
errorのフォーマット
- code [int]: エラーコード、あらかじめ定義されている → error object
- message [string]: エラーメッセージ
- data [any]: 追加情報用のフィールド、型の指定もなく、省略も可能
成功時:
{ "jsonrpc": "2.0", "result": 19, "id": 1 }
失敗時:
{ "jsonrpc": "2.0", "error": { "code": -32601, "message": "Method not found" }, "id": "1" }
通知
リクエスト時にIDフィールドを省略すると、レスポンスを期待しないと解釈され、何も返ってこない。これを「通知」と呼んでいる。
{ "jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5] }
Batch
トップレベルを配列にし、リクエストを複数一気に送ることができる。
リクエスト:
[ {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"}, {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}, {"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"}, {"foo": "boo"}, {"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"}, {"jsonrpc": "2.0", "method": "get_data", "id": "9"} ]
レスポンス:
[ {"jsonrpc": "2.0", "result": 7, "id": "1"}, {"jsonrpc": "2.0", "result": 19, "id": "2"}, {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}, {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "5"}, {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"} ]
例
GoでJSON-RPCを扱う代表的なパッケージは以下の3つ
- jsonrpc - The Go Programming Language
- json2 - Gorilla, the golang web toolkit
- GitHub - osamingo/jsonrpc: The jsonrpc package helps implement of JSON-RPC 2.0
【思ったより手間取っているので追記予定】
疑問
- JSON-RPCを使う理由
- 一般的なidの割り振り方