Novice Blog

死ぬまでずっと途中だ!

【論文紹介】"Ngram2vec: Learning Improved Word Representations from Ngram Co-occurrence Statistics"

EMNLP 2017にacceptされていた論文 "Ngram2vec: Learning Improved Word Representations from Ngram Co-occurrence Statistics"を読んだのでまとめた.

paper
github


Ngram2vec: Learning Improved Word Representations from Ngram Co-occurrence Statistics

  • EMNLP 2017

どんなもの?

word2vecをngramに拡張し,ngramでの分散表現を獲得
SGNS(Skip-gram with Negative Sampling),GloVe,PPMI matrix,PPMI matrix + SVD factorizationの4つの表現方法にngramを導入
ngram embeddingは,semantic meaningsとsyntactic patternsを反映可能
ngramの語彙数は膨大なため,ngram embeddingの計算コストを軽減するため,共起行列を構築する新しい方法を提案

これができるとどう嬉しいの?

対義語やコロケーションをうまくベクトル空間上に表現できる
例 ) 'not interesting' と 'boring' など

f:id:hightensan:20180913212629p:plain

先行研究と比べてどこがすごい?

従来の単語ベースの分散表現獲得モデルは,情報源が単語共起統計量に限定されている(Levy et al.,2015)
情報源を'word-word'型の共起から'ngram-ngram'型の共起まで拡張することで,改善された分散表現を学習可能

SGNS

Skip-gram with negative sampling (SGNS)
(1)コーパスをスキャンし,ローカルウィンドウ内の<word,context>ペアをトレーニングサンプルとして使用
(2)文脈を予測するために(または逆に単語を作るために)モデルを訓練

negative samplingはトレーニングプロセスをスピードアップし,トレーニング時間を数日または数週間から数時間に短縮可能

Glove

<word, context>のペアで訓練された典型的なneural embeddingモデルとは異なり,GloVeは共起行列に基づいて分散表現を学習

GloVeは従来の"words predict contexts"パラダイムを破る
その目的は,マトリックス内の非ゼロ値を再構成すること
マトリックスの直接的な使用は,結果の向上とスピードの向上をもたらすと報告されている
(しかし,word2vecに比べてGloVeの利点については依然として論争がある(Levy et al.,2015; Schnabel et al.,2015))

GloVeおよび他のembeddingモデルは,本質的にコーパスの単語共起統計量に基づいている
<word, context>の組と共起行列は互いに変換することが可能

PPMI & SVD

Levy and Goldberg (2014c)は,SGNSが暗黙的にPMI行列を考慮に入れているだけであることを明らかにした
Levy and Goldberg(2014b)は,正のPMI(PPMI)行列が,一連の言語タスクに新たに提案されたembeddingモデルに依然として匹敵することを示している

PPMI行列から密な単語表現を得るために,スパース行列から低次元ベクトルを学習するための古典的次元削減法であるSVDを用いてPPMI行列を因数分解する

※ PPMI : Pointwise Mutual Information (自己相互情報量)
※ SVD : Singular Value Decomposition (特異値分解)
※ PMIの値が示す2つの事象の関連度合いは, 自然言語処理においては単語の共起性と捉えることができ, さらに単語の意味的な類似性と近似できる(2つの単語が一緒に起こりやすい場合は, 意味的にも関連しているだろうという直感に基づいて).

参考:自然言語処理における自己相互情報量 (Pointwise Mutual Information, PMI)

Ngram in Deep Learning

deep learningの文献では,ngramはテキスト表現を生成するのに有用であることが示されている

特に,畳み込みニューラルネットワーク(CNN)は,一連のNLPタスクで良好に機能することが報告されている(Blunsomら,2014; Huら,2014; Severyn and Moschitti,2015) CNNは基本的にテキストを表すためにngram情報を使用している
1-D畳み込み層を使用してngramの特徴を抽出し,異なる特徴は最大プール層によって選択されている

  • (Li et al.,2016)では,テキスト埋め込みがテキスト中のngramを予測するのに役立つように訓練されたパラグラフベクトルモデルにngram埋め込みが導入されている
  • (Melamud et al.,2014)では,ngram言語モデルを使用してコンテキストをモデル化し,ngramの類似性タスクの有効性を示している 単語埋め込み文献では,Melamudらが関連する研究を行っている。 (2014),ここでは単語埋め込みモデルがベースラインとして使用されます。彼らは,ngram言

フレーズはngramとは異なる
フレーズは明確なセマンティクスを持ち,フレーズの数はngramの数よりはるかに少ない   phrase embeddinggの使用は,word embeddingの品質にほとんど影響を与えない

(It should be noted that phrases are different from ngrams. Phrases have clear semantics and the number of phrases is much less than the number of ngrams. Using phrase embedding has little impact on word embedding’s quality.)

技術や手法のキモはどこ?

  • Ngram Predicts Ngram

f:id:hightensan:20180913212713p:plain

ngram から 周辺のngramを予測

'ngram predicts ngram'の目的関数:

f:id:hightensan:20180913212730p:plain

 C(w_{t:t+n_w})の定義:

f:id:hightensan:20180913212741p:plain

 N_w : the order of center ngram
 N_c : the order of context ngram

  • Co-occurrence Matrix Construction

GloVe, PPMI, PPMI + SVD にngramを導入 → 単語の共起行列をngramのものに置き換えるだけ
<word(ngram), word(ngram)>のペアを取り出した後,これらのペアに共起行列を構築.
残りのステップは元のベースラインモデルと同じ

行列構築のコストは,ペア数(#Pairs)と密接に関連している
Table1は,コーパスwiki2010から抽出されたペア数の統計を示しているが,
ngramを考慮すると#Pairsが大きくなることがわかる

f:id:hightensan:20180913212757p:plain

【対策】

  1. 'mixture' strategy
    単語(またはngram)が頻度順にソートされるとき,共起行列の左上隅は密で残り部分はsparse
    左上隅の密な要素は2D配列に格納され,メモリに格納
    残りのsparseな要素は,<ngram,H>の形式で格納

  2. 'stripes' strategy
    H<context,count>は,ngramとコンテキストが同時に発生する回数を記録する連想配列
    <ngram,context>のペアを明示的に格納する場合と比較して,
    'stripes'strategyは左上隅の外側でペアを集める機会を増やす

f:id:hightensan:20180913212810p:plain

第1段階:ペアは topLeft関数に従って異なるデータ構造に格納.中間結果は,メモリがいっぱいになると一時ファイルに書き込まれる

第2段階:ソートされた一時ファイルをマージして共起行列を生成. getSmallest関数は,テンポラリファイルから最小のキーでペア<ngram,H>を取り出す

どうやって有効だと検証した?

analogy task と similarity task で評価

  • analogy task
    Dataset :
    GOOGLE dataset (Mikolov et al., 2013a) contains 19544 questions
    MSR dataset(Mikolov et al., 2013c) contains 8000 analogy questions

f:id:hightensan:20180913212823p:plain

The SGNS of bi_bi type provides the highest results.
It is very effective on capturing semantic information (Google semantic).
Around 10 percent improvements are witnessed on semantic questions compared with uni_uni baseline.
For syntactic questions (Google syntactic and MSR datasets), around 5 percent improvements are obtained on average.

  1. The effect of overlap
    overlapの影響が大きい
    ウィンドウサイズが2と5のときのnon overlap設定と比較して,約10%と約3%の改善が見られる
    構文上のケースでは,non overlap設定は約5%のマージンで改善される

  2. The effect of trigrams
    トライグラムの導入は,analogy(特にウィンドウサイズ2)のパフォーマンスを低下させる
    これはおそらく,trigramはwiki2010ではスパースであり,10億のトークンと比較して小さなコーパスであると考えられる
    筆者らはhigh order ngramが大規模なコーパスに適していると推測し,その結果を今後の研究で報告する予定
    ※ trigramは,ウィンドウサイズが2のときにnon overlap設定のボキャブラリには含まれないことに注意する必要がある
    単語とtrigramの最短距離は3で,ウィンドウサイズを超えているため

  3. similarity task

f:id:hightensan:20180913212838p:plain

結果は,analogyの場合と同様
bigramの使用は効果的だが,trigramの導入はほとんどの場合パフォーマンスを低下させる
一般的に,bigramは一連の言語作業においてSGNSに対して大幅な改善をもたらす
ngramは伝統的な言語モデリング問題の重要な部分であることが一般的に知られており,table2およびtable3の結果により,より高度なword embeddingモデルであるSGNS上でのngramの有効性を再度確認できる

定性的評価

f:id:hightensan:20180913212856p:plain

Table7は,targetとなるngramおよびそれらの最も近い最近接のリストを示す

As might be expected, synonyms of the target ngrams are returned in top positions (e.g. ‘give off’ and ‘emit’; ‘heavy rain’ and ‘downpours’). From the results of the first group, it can be observed that bigram in negative form ‘not X’ is useful for finding the antonym of word ‘X’. Besides that, the trained ngram embeddings also preserve some common sense. For example, the returned result of ‘highest mountain’ is a list of mountain names (with a few exceptions such as ‘unclimbed’). In terms of syntactic patterns, we can observe that in most cases, the returned ngrams are in the similar form with target ngrams. In general, the trained embeddings basically reflect semantic meanings and syntactic patterns of ngrams.

With high-quality ngram embeddings, we have the opportunity to do more interesting things in our future work. For example, we will construct a antonym dataset to evaluate ngram embeddings systematically. Besides that, we will find more scenarios for using ngram embeddings. In our view, ngram embeddings have potential to be used in many NLP tasks. For example, Johnson and Zhang (2015) use one-hot ngram representation as the input of CNN. Li et al. (2016) use ngram embeddings to represent texts. Intuitively, initializing these models with pre-trained ngram embeddings may further improve the accuracies.

  • targetとなるngramの同義語がベクトル空間上で最上位の位置に返されます
    (例えば,'give off' and 'emit'; 'heavy rain' and 'downpours')
  • 「Xでない」という反意を表すbigramは,単語「X」の反意語を見つけるのに有用であることが分かる
  • 訓練されたngram embeddingはいくつかの常識を保持している(たとえば,'highest mountain'で返された結果は山の名前のリスト)
  • 構文パターン(syntactic patterns)に関しては,ほとんどの場合,返されたngramはtargetとなるngramと同様の形式である.一般に,訓練されたembeddingは,基本的にsemantic meaningsとngramの構文パターン(syntactic patterns)を反映している

高品質のngram embeddingを使用することで,future workとしてもっと興味深いことを達成する機会を得た

  • ngram embeddingを体系的に評価するための反意語データセットの作成
  • ngram embeddingを使用するためのより多くのシナリオ

ngram embeddingは多くのNLPタスクで使用される可能性がある.
たとえば,JohnsonとZhang(2015)は,CNNの入力としてワンホットngram表現を使用する
Liら(2016)は,テキストを表現するためにngram embeddingを使用する
直感的には,事前にトレーニングされたngram embeddingを使用してこれらのモデルを初期化すると,精度がさらに向上する可能性がある

議論はある?

このセクションでは,uni_uni および uni_bi タイプのモデルの結果のみを報告する

f:id:hightensan:20180913212912p:plain

Table4に,analogyとsimilarityのPPMIマトリックスの結果を示す PPMIはMultiplicative(Mul)評価が良くなっている → Mulの結果を分析する

bigramを使用すると,analogy taskで大幅な改善が見られる

  • Googleデータセットでは,bigramは合計精度を10%上回る
    ウィンドウサイズが2のとき,semantic questionsの精度は0.854にも達する(state-of-the-art)
  • MSRデータセットでは,約20%の改善が達成されている
    bigramを使用しても,similarityデータセットが改善されるとは限らない

uni_bi型のPPMI行列は,

  • ウィンドウサイズ2で5つのデータセットの結果を改善する
  • ウィンドウサイズ5では,bigramを使用すると2つのデータセットの結果のみが改善される

f:id:hightensan:20180913212928p:plain

Table5 および Table6 にGloVeおよびSVDの結果を示す

  • GloVeの場合,bigramの導入によるanalogy taskの一貫した(しかしマイナーな)改善が達成されている
  • similarityデータセットでは,ほとんどの場合改善が見られる
  • SVDの場合,bigramは,analogy taskとsimilarity taskの両方で悪い結果につながることがある
  • 一般に,GloVeとSVDでは大きな改善は見られません。
  • nグラムと様々なハイパーパラメータとの間の関係は,さらなる探査を必要とする
    筆者らの事前の推測では,デフォルトのハイパーパラメータ設定が修正されるべき
    ベースラインモデルで使用されているハイパーパラメータに厳密に従いますので,ngramの導入を調整する必要はない
    それに加えて,動的ウィンドウ,重み付け関数の減少,dirty sub-samplingなどのいくつかの一般的な技術は破棄される

次に読むべき論文は?

  • Jun Suzuki and Masaaki Nagata. 2015. Aunified learning framework of skip-grams and global vectors.
    In Proceedings of ACL 2015, Volume 2: Short Papers, pages 186–191.

  • Bofang Li, Zhe Zhao, Tao Liu, Puwei Wang, and Xiaoyong Du. 2016. Weighted neural bag-of-n-grams model: New baselines for text classification.
    In Proceedings of COLING 2016, pages 1591–1600.

  • Oren Melamud, Jacob Goldberger, and Ido Dagan. 2016. context2vec: Learning generic context embedding with bidirectional LSTM.
    In Proceedings of the 20th SIGNLL Conference on Computational Natural Language Learning, CoNLL 2016, Berlin, Germany, August 11-12, 2016, pages 51–61.

  • Fei Sun, Jiafeng Guo, Yanyan Lan, Jun Xu, and Xueqi Cheng. 2016. Inside out: Two jointly predictive models for word representations and phrase representations.
    In Proceedings of AAAI 2016, pages 2821–2827.

参考資料

Word2Vec のニューラルネットワーク学習過程を理解する

PyTorchのチュートリアルをもとに英→日機械翻訳を試す

PyTorchが公式に提供しているチュートリアルの中に
Seq2Seq + Attentionを用いたニューラル機械翻訳がある.

Translation with a Sequence to Sequence Network and Attention — PyTorch Tutorials 0.4.1 documentation

今回はその素晴らしいチュートリアルをもとに
英→日機械翻訳を試してみた.

※ 基本的には公式のチュートリアルが素晴らしく充実しており,とても勉強になります.
 この記事は自身の後学のために公式のチュートリアルを簡潔に適宜修正したものになります.

なお,英日対訳コーパスodashiさんのものを使用させていただいた.

github.com


0. import宣言

import warnings
warnings.filterwarnings('ignore')
import re
import random
import unicodedata
import string

import torch
import torch.nn as nn
from torch import optim
import torch.nn.functional as F

import numpy as np
from nltk import bleu_score
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
plt.style.use('ggplot')

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

1. データ読み込み

lines = open('data/en-ja.txt', encoding='utf-8').read().strip().split('\n')
test_ratio = 0.1
train_lines, test_lines = lines[:int(len(lines)*(1-test_ratio))], lines[int(len(lines)*(1-test_ratio)):]

pairs = [l.split('\t') for l in lines]
training_pairs = [l.split('\t') for l in train_lines]
test_pairs = [l.split('\t') for l in test_lines]

input_sentences = [s[0] for s in pairs]
output_sentences = [s[1] for s in pairs]
input_sentences[:5]

["i can 't tell who will arrive first .",
'many animals have been destroyed by men .',
"i 'm in the tennis club .",
'emi looks happy .',
'please bear this fact in mind .']

output_sentences[:5]

['誰 が 一番 に 着 く か 私 に は 分か り ま せ ん 。',
'多く の 動物 が 人間 に よ っ て 滅ぼ さ れ た 。',
'私 は テニス 部員 で す 。',
'エミ は 幸せ そう に 見え ま す 。',
'この 事実 を 心 に 留め て お い て 下さ い 。']

2. 単語→ID化

SOS_token = 0
EOS_token = 1

class Lang:
    def __init__(self, name):
        self.name = name
        self.word2index = {}
        self.word2count = {}
        self.index2word = {0: "<s>", 1: "</s>"}
        self.n_words = 2  # Count SOS and EOS

    def addSentence(self, sentence):
        for word in sentence.split(' '):
            self.addWord(word)

    def addWord(self, word):
        if word not in self.word2index:
            self.word2index[word] = self.n_words
            self.word2count[word] = 1
            self.index2word[self.n_words] = word
            self.n_words += 1
        else:
            self.word2count[word] += 1
input_lang = Lang("en")
output_lang = Lang("ja")
for pair in pairs:
    input_lang.addSentence(pair[0])
    output_lang.addSentence(pair[1])
print("翻訳元コーパス({})の語彙数:{}".format(input_lang.name,input_lang.n_words))
print("翻訳先コーパス({})の語彙数:{}".format(output_lang.name,output_lang.n_words))

翻訳元コーパス(en)の語彙数:6636
翻訳先コーパス(ja)の語彙数:8776

input_sentences_wordlengths = [len(s.split()) for s in input_sentences]
output_sentences_wordlengths = [len(s.split()) for s in output_sentences]
print("翻訳元コーパス({})の最小文章長:{},最大文章長:{}".format(input_lang.name,min(input_sentences_wordlengths),max(input_sentences_wordlengths)))  
print("翻訳先コーパス({})の最小文章長:{},最大文章長:{}".format(output_lang.name,min(output_sentences_wordlengths),max(output_sentences_wordlengths)))

翻訳元コーパス(en)の最小文章長:4,最大文章長:16
翻訳先コーパス(ja)の最小文章長:4,最大文章長:16

MIN_LENGTH = 4 + 2  # <s>,</s>
MAX_LENGTH = 16 + 2 # <s>,</s>
for i in list(input_lang.index2word.keys())[:5]:
    print("{}:{}".format(i,input_lang.index2word[i]))
print("...")

0:<s>
1:</s>
2:i
3:can
4:'t
...

for i in list(output_lang.index2word.keys())[:5]:
    print("{}:{}".format(i,output_lang.index2word[i]))
print("...")

0:<s>
1:</s>
2:誰
3:が
4:一番
...


3. Seq2Seqモデル(Attention)

class EncoderRNN(nn.Module):
    def __init__(self, input_size, hidden_size):
        super(EncoderRNN, self).__init__()
        self.hidden_size = hidden_size

        self.embedding = nn.Embedding(input_size, hidden_size)
        self.gru = nn.GRU(hidden_size, hidden_size)

    def forward(self, input, hidden):
        embedded = self.embedding(input).view(1, 1, -1)
        output = embedded
        output, hidden = self.gru(output, hidden)
        return output, hidden

    def initHidden(self):
        return torch.zeros(1, 1, self.hidden_size, device=device)
class AttnDecoderRNN(nn.Module):
    def __init__(self, hidden_size, output_size, dropout_p=0.2, max_length=MAX_LENGTH):
        super(AttnDecoderRNN, self).__init__()
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.dropout_p = dropout_p
        self.max_length = max_length

        self.embedding = nn.Embedding(self.output_size, self.hidden_size)
        self.attn = nn.Linear(self.hidden_size * 2, self.max_length)
        self.attn_combine = nn.Linear(self.hidden_size * 2, self.hidden_size)
        self.dropout = nn.Dropout(self.dropout_p)
        self.gru = nn.GRU(self.hidden_size, self.hidden_size)
        self.out = nn.Linear(self.hidden_size, self.output_size)

    def forward(self, input, hidden, encoder_outputs):
        embedded = self.embedding(input).view(1, 1, -1)
        embedded = self.dropout(embedded)

        attn_weights = F.softmax(
            self.attn(torch.cat((embedded[0], hidden[0]), 1)), dim=1)
        attn_applied = torch.bmm(attn_weights.unsqueeze(0),
                                 encoder_outputs.unsqueeze(0))

        output = torch.cat((embedded[0], attn_applied[0]), 1)
        output = self.attn_combine(output).unsqueeze(0)

        output = F.relu(output)
        output, hidden = self.gru(output, hidden)

        output = F.log_softmax(self.out(output[0]), dim=1)
        return output, hidden, attn_weights

    def initHidden(self):
        return torch.zeros(1, 1, self.hidden_size, device=device)

4. Training

hidden_size = 512
encoder = EncoderRNN(input_lang.n_words, hidden_size).to(device)
decoder = AttnDecoderRNN(hidden_size, output_lang.n_words, dropout_p=0.2).to(device)
encoder

EncoderRNN(
(embedding): Embedding(6636, 512)
(gru): GRU(512, 512)
)

decoder

AttnDecoderRNN(
(embedding): Embedding(8776, 512)
(attn): Linear(in_features=1024, out_features=18, bias=True)
(attn_combine): Linear(in_features=1024, out_features=512, bias=True)
(dropout): Dropout(p=0.2)
(gru): GRU(512, 512)
(out): Linear(in_features=512, out_features=8776, bias=True)
)

def tensorsFromPair(pair):
    input_sentence = pair[0]
    input_indexes = [input_lang.word2index[word] for word in input_sentence.split(' ')]
    input_indexes.append(EOS_token)
    input_tensor = torch.tensor(input_indexes, dtype=torch.long, device=device).view(-1, 1)
    
    output_sentence = pair[1]
    output_indexes = [output_lang.word2index[word] for word in output_sentence.split(' ')]
    output_indexes.append(EOS_token)
    output_tensor = torch.tensor(output_indexes, dtype=torch.long, device=device).view(-1, 1)

    return (input_tensor, output_tensor)
n_iters = 50000
learning_rate=0.01

encoder_optimizer = optim.SGD(encoder.parameters(), lr=learning_rate)
decoder_optimizer = optim.SGD(decoder.parameters(), lr=learning_rate)
training_tensor_pairs = [tensorsFromPair(random.choice(training_pairs)) for i in range(n_iters)] # n_iters数分ランダムサンプリング
criterion = nn.NLLLoss()
print('< input tensor example >')
print(training_tensor_pairs[0][0])
print()
print('< output tensor example >')
print(training_tensor_pairs[0][1])

< input tensor example >
tensor([[ 2],
[600],
[257],
[ 42],
[141],
[258],
[ 10],
[ 1]], device='cuda:0')
 
< output tensor example >
tensor([[248],
[ 30],
[177],
[ 47],
[ 13],
[ 31],
[ 16],
[ 1]], device='cuda:0')

def train(input_tensor, target_tensor, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion, max_length=MAX_LENGTH, teacher_forcing_ratio = 1.0):
    encoder_hidden = encoder.initHidden()

    encoder_optimizer.zero_grad()
    decoder_optimizer.zero_grad()

    input_length = input_tensor.size(0)
    target_length = target_tensor.size(0)

    encoder_outputs = torch.zeros(max_length, encoder.hidden_size, device=device)

    loss = 0
    for ei in range(input_length):
        encoder_output, encoder_hidden = encoder(input_tensor[ei], encoder_hidden)
        encoder_outputs[ei] = encoder_output[0, 0]

    decoder_input = torch.tensor([[SOS_token]], device=device)
    decoder_hidden = encoder_hidden # 最後の時刻の隠れ層を使用
    decoder_outputs = []

    # Teacher forcing
    use_teacher_forcing = True if random.random() < teacher_forcing_ratio else False
    if use_teacher_forcing:
        # Use teacher forcing
        for di in range(target_length):
            decoder_output, decoder_hidden, decoder_attention = decoder(decoder_input, decoder_hidden, encoder_outputs)
            decoder_outputs.append(decoder_output)
            loss += criterion(decoder_output, target_tensor[di])
            
            decoder_input = target_tensor[di]
    else:
        # Without teacher forcing
        for di in range(target_length):
            decoder_output, decoder_hidden, decoder_attention = decoder(decoder_input, decoder_hidden, encoder_outputs)
            decoder_outputs.append(decoder_output)
            topv, topi = decoder_output.topk(1)
            decoder_input = topi.squeeze().detach()
            
            loss += criterion(decoder_output, target_tensor[di])
            if decoder_input.item() == EOS_token:
                break

    loss.backward()
    encoder_optimizer.step()
    decoder_optimizer.step()

    return loss.item() / target_length, decoder_outputs
def calc_bleu(target_tensor, decoder_tensor):
    refs = [[output_lang.index2word[t] for t in target_tensor.cpu().numpy()]]
    hyps = [output_lang.index2word[t] for t in decoder_tensor.cpu().numpy()]
    return 100 * bleu_score.sentence_bleu(refs, hyps)
print_every=1000
plot_every=200

print_loss_total = 0
print_bleu_total = 0
plot_loss_total = 0
plot_bleu_total = 0
loss_history = []
bleu_history = []
for iter in range(1, n_iters + 1):
    training_tensor_pair = training_tensor_pairs[iter-1]
    input_tensor = training_tensor_pair[0]
    target_tensor = training_tensor_pair[1]

    loss, decoder_outputs = train(input_tensor, target_tensor, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion)
    decoder_tensor = torch.tensor([i.topk(1)[1] for i in decoder_outputs])
    target_tensor = target_tensor.view(-1,target_tensor.shape[0])[0]
    
    bleu = calc_bleu(target_tensor, decoder_tensor)
    
    print_loss_total += loss
    print_bleu_total += bleu
    
    plot_loss_total += loss
    plot_bleu_total += bleu
    
    if iter % print_every == 0:
        print_loss_avg = print_loss_total / print_every
        print_bleu_avg = print_bleu_total / print_every
        print_loss_total = 0
        print_bleu_total = 0
        print('iter=%6d progress=%3d%% : loss= %.4f, bleu= %3f' % (iter, iter / n_iters * 100, print_loss_avg, print_bleu_avg))
        
    if iter % plot_every == 0:
        plot_loss_avg = plot_loss_total / plot_every
        plot_bleu_avg = plot_bleu_total / plot_every
        loss_history.append(plot_loss_avg)
        bleu_history.append(plot_bleu_avg)
        plot_loss_total = 0
        plot_bleu_total = 0

iter= 1000 progress= 2% : loss= 4.3269, bleu= 1.871565
iter= 2000 progress= 4% : loss= 3.6614, bleu= 4.311060
iter= 3000 progress= 6% : loss= 3.4342, bleu= 5.337739
iter= 4000 progress= 8% : loss= 3.2607, bleu= 7.349442
iter= 5000 progress= 10% : loss= 3.1763, bleu= 8.106195
iter= 6000 progress= 12% : loss= 3.0579, bleu= 8.004884
iter= 7000 progress= 14% : loss= 2.9973, bleu= 9.615947
iter= 8000 progress= 16% : loss= 2.8690, bleu= 10.995813
iter= 9000 progress= 18% : loss= 2.8181, bleu= 12.018116
iter= 10000 progress= 20% : loss= 2.7526, bleu= 12.019394
iter= 11000 progress= 22% : loss= 2.7032, bleu= 12.164422
iter= 12000 progress= 24% : loss= 2.5519, bleu= 14.942523
iter= 13000 progress= 26% : loss= 2.6454, bleu= 14.096671
iter= 14000 progress= 28% : loss= 2.5556, bleu= 14.323362
iter= 15000 progress= 30% : loss= 2.5182, bleu= 14.667089
iter= 16000 progress= 32% : loss= 2.4339, bleu= 15.752523
iter= 17000 progress= 34% : loss= 2.4273, bleu= 15.552970
iter= 18000 progress= 36% : loss= 2.3719, bleu= 17.833917
iter= 19000 progress= 38% : loss= 2.3144, bleu= 17.715252
iter= 20000 progress= 40% : loss= 2.3099, bleu= 18.025124
iter= 21000 progress= 42% : loss= 2.2757, bleu= 18.679683
iter= 22000 progress= 44% : loss= 2.2545, bleu= 19.556337
iter= 23000 progress= 46% : loss= 2.2239, bleu= 20.163076
iter= 24000 progress= 48% : loss= 2.2223, bleu= 18.929656
iter= 25000 progress= 50% : loss= 2.1114, bleu= 21.771670
iter= 26000 progress= 52% : loss= 2.1453, bleu= 19.853710
iter= 27000 progress= 54% : loss= 2.1209, bleu= 21.419971
iter= 28000 progress= 56% : loss= 2.0911, bleu= 21.514201
iter= 29000 progress= 57% : loss= 2.0828, bleu= 21.203588
iter= 30000 progress= 60% : loss= 2.1051, bleu= 21.578546
iter= 31000 progress= 62% : loss= 2.1040, bleu= 21.865763
iter= 32000 progress= 64% : loss= 2.0355, bleu= 21.806510
iter= 33000 progress= 66% : loss= 2.0578, bleu= 22.580487
iter= 34000 progress= 68% : loss= 2.0244, bleu= 22.418764
iter= 35000 progress= 70% : loss= 1.9706, bleu= 24.380544
iter= 36000 progress= 72% : loss= 1.9508, bleu= 25.039053
iter= 37000 progress= 74% : loss= 1.9714, bleu= 23.014639
iter= 38000 progress= 76% : loss= 1.9420, bleu= 24.265087
iter= 39000 progress= 78% : loss= 1.9473, bleu= 23.563979
iter= 40000 progress= 80% : loss= 1.9383, bleu= 23.739913
iter= 41000 progress= 82% : loss= 1.9311, bleu= 24.746721
iter= 42000 progress= 84% : loss= 1.9502, bleu= 23.907421
iter= 43000 progress= 86% : loss= 1.8771, bleu= 25.822816
iter= 44000 progress= 88% : loss= 1.8734, bleu= 25.263204
iter= 45000 progress= 90% : loss= 1.8831, bleu= 24.645873
iter= 46000 progress= 92% : loss= 1.8579, bleu= 25.760848
iter= 47000 progress= 94% : loss= 1.8771, bleu= 24.935368
iter= 48000 progress= 96% : loss= 1.8772, bleu= 24.196793
iter= 49000 progress= 98% : loss= 1.9398, bleu= 23.801817
iter= 50000 progress=100% : loss= 1.8306, bleu= 25.320334

x_loss = np.array(range(len(loss_history)))
x_bleu = np.array(range(len(bleu_history)))
fig, (axL, axR) = plt.subplots(ncols=2, figsize=(16,4))

axL.plot(x_loss*plot_every, loss_history, linewidth=1)
axL.set_title('loss')
axL.set_xlabel('iter')
axL.set_ylabel('loss')
axL.grid(True)

axR.plot(x_bleu*plot_every, bleu_history, linewidth=1)
axR.set_title('BLEU')
axR.set_xlabel('iter')
axR.set_ylabel('bleu')
axR.grid(True)

fig.show()

f:id:hightensan:20180911124037p:plain


5. Predict

def tensorFromSentence(lang, sentence):
    indexes = [lang.word2index[word] for word in sentence.split(' ')]
    indexes.append(EOS_token)
    return torch.tensor(indexes, dtype=torch.long, device=device).view(-1, 1)
def predict(encoder, decoder, sentence, max_length=MAX_LENGTH):
    with torch.no_grad():
        input_tensor = tensorFromSentence(input_lang, sentence)
        input_length = input_tensor.size()[0]
        encoder_hidden = encoder.initHidden()

        encoder_outputs = torch.zeros(max_length, encoder.hidden_size, device=device)

        for ei in range(input_length):
            encoder_output, encoder_hidden = encoder(input_tensor[ei],
                                                     encoder_hidden)
            encoder_outputs[ei] += encoder_output[0, 0]

        decoder_input = torch.tensor([[SOS_token]], device=device)  # SOS

        decoder_hidden = encoder_hidden

        decoded_words = []
        decoder_attentions = torch.zeros(max_length, max_length)

        for di in range(max_length):
            decoder_output, decoder_hidden, decoder_attention = decoder(decoder_input, decoder_hidden, encoder_outputs)
            decoder_attentions[di] = decoder_attention.data
            topv, topi = decoder_output.data.topk(1)
            if topi.item() == EOS_token:
                decoded_words.append('</s>')
                break
            else:
                decoded_words.append(output_lang.index2word[topi.item()])

            decoder_input = topi.squeeze().detach()

        return decoded_words, decoder_attentions[:di + 1]
def predictRandomly(encoder, decoder, n=10):
    for i in range(n):
        pair = random.choice(test_pairs)
        print('>', pair[0])
        print('=', pair[1])
        output_words, attentions = predict(encoder, decoder, pair[0])
        output_sentence = ' '.join(output_words)
        print('<', output_sentence)
        print('')
predictRandomly(encoder, decoder)

> all the students study english .
= その 学生 たち は 全員 英語 を 勉強 し て い ま す 。
< 学生 の 勉強 は 英語 の 勉強 を する こと に し て い る 。 \</s>
 
> please don 't be sad any more .
= これ 以上 悲し ま な い で 。
< もう これ 以上 は いけ な い 。 \</s>
 
> my father gave a nice watch to me .
= 私 の 父 は 私 に 素敵 な 時計 を くれ た 。
< 父 は 私 に 時計 を くれ た 。 \</s>
 
> translate this book into english .
= この 本 を 英語 に し なさ い 。
< この 本 を あなた は この 本 を 書 い た 。 \</s>
   
> he is unmarried .
= 彼 は 結婚 し て な い で す 。
< 彼 は 頭 が い い 。 \</s>
 
> are you satisfied with the result ?
= あなた は その 結果 に 満足 し て い ま す か 。
< あなた は その 結果 に 賛成 で す か 。 \</s>
 
> you never know what will happen tomorrow .
= 明日 何 が 起こ る か なんて だれ も わか ら な い 。
< 明日 は 何 を する こと が わか ら な い 。 \</s>
 
> my feet went to sleep and i could not stand up .
= 足 が しびれ て 立て な かっ た 。
< 私 の 計画 は 、 あまり 気 に は な い 。 \</s>

> i invited jane to dinner .
= 私 は 夕食 に ジェーン を 招待 し た 。
< ジェーン と ジェーン は 私 に 会 っ た 。 \</s>
 
> remember to post the letter .
= 忘れ ず に その 手紙 を 投函 し て くださ い 。
< 手紙 を 書 き なさ い 。 \</s>
 

 

 
 
notebook形式でも公開している.
https://gist.github.com/hightensan/23d791cefcfa1d12fe27fb1549ebd7eb
 
 
ご査証くださいませ.m(_ _)m

PyCon mini Osaka 2018で発表してきた

(イベントからかなり時間が経過しましたが...)

PyCon mini Osaka 2018で登壇・発表させていただきましたので,
その資料を公開させていただきます.

osaka.pycon.jp

機械学習システムやその説明性・透明性に関するツールである
LIMEとSHAPに関する発表です.

github.com

github.com

 
 
機械学習システムの説明性(Interpretability)に関する研究は
ICMLやNIPSなどでも注目を浴びているので,キャッチアップしていきたいですね.


 
 
簡単なusageもちょろっと紹介しています.
 
 

【論文紹介】"CHARAGRAM: Embedding Words and Sentences via Character n-grams"

EMNLP 2016にacceptされていた論文
"CHARAGRAM : Embedding Words and Sentences via Character n-grams"
を読んだのでまとめた.

paper : https://arxiv.org/abs/1607.02789
github : https://github.com/jwieting/charagram


CHARAGRAM : Embedding Words and Sentences via Character n-grams

  • EMNLP 2016

どんなもの?

文字レベルのn-gramを用いてtextual sequencesを
低次元のembeddingへと変換するCHARAGRAMという手法を提案

これができるとどう嬉しいの?

  • word embedding modelにsubword information(部分文字列)もモデルに組み込みたい

    • knowledge-based morphological features
      (Alexandrescu and Kirchhoff, 2006; El-Desoky Mousa et al.,2013)
    • learned embeddings jointly for subword units and words
      (Lazaridou et al., 2013; Botha and Blunsom, 2014; Qiu et al., 2014; Chen et al., 2015)
  • 近年は,character sequencesを組み込むのがトレンドに

    • Bidirectional long short-term memory (Bi-LSTM) on characters to embed arbitrary word types,
      showing strong performance for language modeling and POS tagging.

 → subword information + character sequences = CHARAGRAM

先行研究と比べてどこがすごい?

自然言語処理タスクにおいては「単語」が最小の単位として扱われ,
そのword embeddingを用いた研究がほとんど.
いくつかの先行研究では文字ベースの特徴を扱う Recurrent Neural Network (RNN) や Convolutional Neural Network (CNN) などが存在.

本研究では CHARAGRAM という,文字レベルのn-gramをシンプルな非線形関数に入力することで低次元のembeddingを獲得し,
単語や文の類似度算出,品詞タグ付けといったタスクに応用している.

【利点】
文字レベルのn-gramを用いているため,

  • 語の順序といった性質が保存
  • 辞書の語彙内に存在しない単語についても対処可能

技術や手法のキモはどこ?

  • 文字レベルのn-gramの使用
  • シンプルな非線形関数を用いて低次元のembeddingを獲得

どうやって有効だと検証した?

  1. word similarity
  2. sentence similarity
  3. part-of-speech tagging
    の3つのタスクで評価

【比較手法】

  • Character level LSTM based model
  • Character level CNN based model

CHARAGRAM が比較手法をout-perform

議論はある?

  • 文字ベースn-gramの畳み込みフィルタと文字ベースn-gramCHARAGRAM は似たものとなってはいるが,
    CNNではすべての文に影響を与える一方で CHARAGRAM は対象となるベクトル表現にのみ影響を与える.

  • CHARAGRAM は学習の収束がとても速い

  • Character level LSTM based model, Character level CNN based modelはとても多くのパラメータを有しているが,
    CHARAGRAM はとても少ないパラメータで優位な結果

  • 単語ベースの特徴を用いる PARAGRAM-PHRASE では辞書の語彙内に存在しないような単語やスペルミスなどが含まれている場合性能が落ちるが,
    文字ベースの特徴を用いているCHARAGRAMでは性能の低下が抑制

  • CHARAGRAM は単語ベースの PARAGRAM-PHRASE と違い単語の順番を保存する性質があるため,
    ある単語についてNearest neighborで近いベクトルを探したところより特徴を掴んだ語を得ることが可能

次に読むべき論文は?

【論文紹介】"The Road to Success: Assessing the Fate of Linguistic Innovations in Online Communities"

COLING 2018にacceptされていた論文
"The Road to Success: Assessing the Fate of Linguistic Innovations in Online Communities"
を読んだのでまとめた.

paper : https://arxiv.org/abs/1806.05838
github : https://github.com/marcodel13/The-Road-to-Success


The Road to Success: Assessing the Fate of Linguistic Innovations in Online Communities

  • COLING 2018

どんなもの?

新たに出現した語が普及/定着(Linguitic Innovation)するかを,
グラフネットワーク上の発言者のポジションに着目し分析・予測した論文

グラフネットワークとしてRedditのデータ,
追跡する語としてネットスラングを対象にしており,
グラフネットワークにおいてどのようなユーザーによって発言された方が
発言者が増加するかを分析している.
ネットスラングの例:iap (In App Purchase)