シーケンスのラベル付け、入力シーケンスの各フレームのカテゴリを予測します。 OCR (Optical Character Recognition 光学式文字認識)。
MIT 音声言語システム研究グループのロブ・カッセルが収集し、スタンフォード大学人工知能研究所のベン・タスカルが OCR データセット (http://ai.stanford.edu/~btaskar/ocr/) を前処理しました。手書きの小文字、各サンプルは 16X8 ピクセルのバイナリ イメージに対応します。単語行はシーケンスを組み合わせたものであり、そのシーケンスが単語に対応します。長さが 14 文字以内の 6800 語。 gzip 圧縮されており、内容はタブ区切りのテキスト ファイルです。 Python csv モジュールは直接読み取ります。ファイルの各行には、1 つの正規化された文字属性、ID 番号、ラベル、ピクセル値、次の文字 ID 番号などが含まれます。
次の文字 ID 値の並べ替えでは、各単語の文字を正しい順序で読み取ります。文字は、次の ID 対応フィールドが設定されなくなるまで収集されます。新しいシーケンスを読み取ります。ターゲット文字とデータ ピクセルを読み取った後、シーケンス オブジェクトにゼロのイメージを入力します。これは、2 つの大きなターゲット文字のすべてのピクセル データの NumPy 配列に含めることができます。
ソフトマックス層はタイムステップ間で共有されます。データ配列とターゲット配列にはシーケンスが含まれており、ターゲット文字ごとに 1 つの画像フレームが含まれます。 RNN 拡張機能。各文字出力にソフトマックス分類子を追加します。分類子は、シーケンス全体ではなく、データの各フレームで予測を評価します。シーケンスの長さを計算します。ソフトマックス層はすべてのフレームに追加されます。いくつかの異なる分類子がすべてのフレームに追加されるか、すべてのフレームが同じ分類子を共有します。共有分類子を使用すると、トレーニング中にトレーニング単語の文字ごとに重みが何度も調整されます。全結合層重み行列のディメンション、batch_size*in_size*out_size。ここで、batch_size と sequence_steps の 2 つの入力次元で重み行列を更新する必要があります。入力 (RNN 出力アクティビティ値) を、batch_size*sequence_steps*in_size の形に平坦化します。重み行列は、より大きなデータのバッチになります。結果は平坦ではありません。
コスト関数。シーケンスの各フレームには、対応する次元で平均化された予測ターゲット ペアがあります。テンソル長(シーケンスの最大長)に従って正規化された tf.reduce_mean は使用できません。実際の系列長に応じて正規化し、手動で tf.reduce_sum と除算演算手段を呼び出す必要があります。
損失関数、tf.argmax は軸 1 ではなく軸 2 用であり、各フレームが埋められ、平均はシーケンスの実際の長さに基づいて計算されます。 tf.reduce_mean は、バッチ データ内のすべての単語の平均を取得します。
TensorFlow の自動微分計算では、シーケンス分類に同じ最適化操作を使用でき、新しいコスト関数を置き換えるだけで済みます。すべての RNN 勾配をカットして、トレーニングの発散を防ぎ、悪影響を回避します。
モデルをトレーニングし、get_sataset は手書き画像、前処理、小文字のワンホット エンコーディング ベクトルをダウンロードします。データの順序をランダムに変更し、トレーニング セットとテスト セットに分割します。
単語の隣接する文字間には依存関係(または相互情報)があり、RNN は同じ単語のすべての入力情報を暗黙的なアクティビティ値に保存します。最初の数文字の分類では、ネットワークには追加情報を推論するための大量の入力がありませんが、双方向 RNN が欠点を克服します。
2 つの RNN は入力シーケンスを観察し、1 つは通常の順序で左端から単語を読み取り、もう 1 つは逆の順序で右端から単語を読み取ります。タイム ステップごとに 2 つの出力アクティビティ値が取得されます。共有ソフトマックス層に送信する前のスプライシング。分類器は各文字から完全な単語情報を取得します。 tf.modle.rnn.bidirection_rnn が実装されています。
双方向 RNN を実装します。予測属性を 2 つの関数に分割し、より少ないコンテンツに焦点を当てます。 _shared_softmax 関数は、関数テンソル データを渡して入力サイズを推測します。他のアーキテクチャ機能を再利用することで、同じ平坦化手法がすべてのタイム ステップで同じソフトマックス層を共有します。 rnn.dynamic_rnn は 2 つの RNN を作成します。
シーケンスの反転は、新しいリバース パス RNN 操作を実装するよりも簡単です。 tf.reverse_sequence 関数は、フレーム データ内の sequence_lengths フレームを反転します。データ フロー ダイアグラムのノードには名前があります。スコープ パラメーターは rnn_dynamic_cell 変数のスコープ名で、デフォルト値は RNN です。 2 つのパラメーターは RNN では異なり、異なるドメインが必要です。
反転シーケンスは後方 RNN に供給され、ネットワーク出力は反転され、前方出力と位置合わせされます。 RNN ニューロン出力次元に沿って 2 つのテンソルを連結して返します。双方向 RNN モデルのパフォーマンスが向上します。
import gzipimport csvimport numpy as npfrom helpers import downloadclass OcrDataset: URL = 'http://ai.stanford.edu/~btaskar/ocr/letter.data.gz'def __init__(self, cache_dir): path = download(type(self).URL, cache_dir) lines = self._read(path) data, target = self._parse(lines) self.data, self.target = self._pad(data, target) @staticmethoddef _read(filepath): with gzip.open(filepath, 'rt') as file_: reader = csv.reader(file_, delimiter='\t') lines = list(reader)return lines @staticmethoddef _parse(lines): lines = sorted(lines, key=lambda x: int(x[0])) data, target = [], [] next_ = Nonefor line in lines:if not next_: data.append([]) target.append([])else:assert next_ == int(line[0]) next_ = int(line[2]) if int(line[2]) > -1 else None pixels = np.array([int(x) for x in line[6:134]]) pixels = pixels.reshape((16, 8)) data[-1].append(pixels) target[-1].append(line[1])return data, target @staticmethoddef _pad(data, target): max_length = max(len(x) for x in target) padding = np.zeros((16, 8)) data = [x + ([padding] * (max_length - len(x))) for x in data] target = [x + ([''] * (max_length - len(x))) for x in target]return np.array(data), np.array(target)import tensorflow as tffrom helpers import lazy_propertyclass SequenceLabellingModel:def __init__(self, data, target, params): self.data = data self.target = target self.params = params self.prediction self.cost self.error self.optimize @lazy_propertydef length(self): used = tf.sign(tf.reduce_max(tf.abs(self.data), reduction_indices=2)) length = tf.reduce_sum(used, reduction_indices=1) length = tf.cast(length, tf.int32)return length @lazy_propertydef prediction(self): output, _ = tf.nn.dynamic_rnn( tf.nn.rnn_cell.GRUCell(self.params.rnn_hidden), self.data, dtype=tf.float32, sequence_length=self.length, )# Softmax layer.max_length = int(self.target.get_shape()[1]) num_classes = int(self.target.get_shape()[2]) weight = tf.Variable(tf.truncated_normal( [self.params.rnn_hidden, num_classes], stddev=0.01)) bias = tf.Variable(tf.constant(0.1, shape=[num_classes]))# Flatten to apply same weights to all time steps.output = tf.reshape(output, [-1, self.params.rnn_hidden]) prediction = tf.nn.softmax(tf.matmul(output, weight) + bias) prediction = tf.reshape(prediction, [-1, max_length, num_classes])return prediction @lazy_propertydef cost(self):# Compute cross entropy for each frame.cross_entropy = self.target * tf.log(self.prediction) cross_entropy = -tf.reduce_sum(cross_entropy, reduction_indices=2) mask = tf.sign(tf.reduce_max(tf.abs(self.target), reduction_indices=2)) cross_entropy *= mask# Average over actual sequence lengths.cross_entropy = tf.reduce_sum(cross_entropy, reduction_indices=1) cross_entropy /= tf.cast(self.length, tf.float32)return tf.reduce_mean(cross_entropy) @lazy_propertydef error(self): mistakes = tf.not_equal( tf.argmax(self.target, 2), tf.argmax(self.prediction, 2)) mistakes = tf.cast(mistakes, tf.float32) mask = tf.sign(tf.reduce_max(tf.abs(self.target), reduction_indices=2)) mistakes *= mask# Average over actual sequence lengths.mistakes = tf.reduce_sum(mistakes, reduction_indices=1) mistakes /= tf.cast(self.length, tf.float32)return tf.reduce_mean(mistakes) @lazy_propertydef optimize(self): gradient = self.params.optimizer.compute_gradients(self.cost)try: limit = self.params.gradient_clipping gradient = [ (tf.clip_by_value(g, -limit, limit), v)if g is not None else (None, v)for g, v in gradient]except AttributeError:print('No gradient clipping parameter specified.') optimize = self.params.optimizer.apply_gradients(gradient)return optimizeimport randomimport tensorflow as tfimport numpy as npfrom helpers import AttrDictfrom OcrDataset import OcrDatasetfrom SequenceLabellingModel import SequenceLabellingModelfrom batched import batched params = AttrDict( rnn_cell=tf.nn.rnn_cell.GRUCell, rnn_hidden=300, optimizer=tf.train.RMSPropOptimizer(0.002), gradient_clipping=5, batch_size=10, epochs=5, epoch_size=50)def get_dataset(): dataset = OcrDataset('./ocr')# Flatten images into vectors.dataset.data = dataset.data.reshape(dataset.data.shape[:2] + (-1,))# One-hot encode targets.target = np.zeros(dataset.target.shape + (26,))for index, letter in np.ndenumerate(dataset.target):if letter: target[index][ord(letter) - ord('a')] = 1dataset.target = target# Shuffle order of examples.order = np.random.permutation(len(dataset.data)) dataset.data = dataset.data[order] dataset.target = dataset.target[order]return dataset# Split into training and test data.dataset = get_dataset() split = int(0.66 * len(dataset.data)) train_data, test_data = dataset.data[:split], dataset.data[split:] train_target, test_target = dataset.target[:split], dataset.target[split:]# Compute graph._, length, image_size = train_data.shape num_classes = train_target.shape[2] data = tf.placeholder(tf.float32, [None, length, image_size]) target = tf.placeholder(tf.float32, [None, length, num_classes]) model = SequenceLabellingModel(data, target, params) batches = batched(train_data, train_target, params.batch_size) sess = tf.Session() sess.run(tf.initialize_all_variables())for index, batch in enumerate(batches): batch_data = batch[0] batch_target = batch[1] epoch = batch[2]if epoch >= params.epochs:breakfeed = {data: batch_data, target: batch_target} error, _ = sess.run([model.error, model.optimize], feed)print('{}: {:3.6f}%'.format(index + 1, 100 * error)) test_feed = {data: test_data, target: test_target} test_error, _ = sess.run([model.error, model.optimize], test_feed)print('Test error: {:3.6f}%'.format(100 * error))import tensorflow as tffrom helpers import lazy_propertyclass BidirectionalSequenceLabellingModel:def __init__(self, data, target, params): self.data = data self.target = target self.params = params self.prediction self.cost self.error self.optimize @lazy_propertydef length(self): used = tf.sign(tf.reduce_max(tf.abs(self.data), reduction_indices=2)) length = tf.reduce_sum(used, reduction_indices=1) length = tf.cast(length, tf.int32)return length @lazy_propertydef prediction(self): output = self._bidirectional_rnn(self.data, self.length) num_classes = int(self.target.get_shape()[2]) prediction = self._shared_softmax(output, num_classes)return predictiondef _bidirectional_rnn(self, data, length): length_64 = tf.cast(length, tf.int64) forward, _ = tf.nn.dynamic_rnn( cell=self.params.rnn_cell(self.params.rnn_hidden), inputs=data, dtype=tf.float32, sequence_length=length, scope='rnn-forward') backward, _ = tf.nn.dynamic_rnn( cell=self.params.rnn_cell(self.params.rnn_hidden), inputs=tf.reverse_sequence(data, length_64, seq_dim=1), dtype=tf.float32, sequence_length=self.length, scope='rnn-backward') backward = tf.reverse_sequence(backward, length_64, seq_dim=1) output = tf.concat(2, [forward, backward])return outputdef _shared_softmax(self, data, out_size): max_length = int(data.get_shape()[1]) in_size = int(data.get_shape()[2]) weight = tf.Variable(tf.truncated_normal( [in_size, out_size], stddev=0.01)) bias = tf.Variable(tf.constant(0.1, shape=[out_size]))# Flatten to apply same weights to all time steps.flat = tf.reshape(data, [-1, in_size]) output = tf.nn.softmax(tf.matmul(flat, weight) + bias) output = tf.reshape(output, [-1, max_length, out_size])return output @lazy_propertydef cost(self):# Compute cross entropy for each frame.cross_entropy = self.target * tf.log(self.prediction) cross_entropy = -tf.reduce_sum(cross_entropy, reduction_indices=2) mask = tf.sign(tf.reduce_max(tf.abs(self.target), reduction_indices=2)) cross_entropy *= mask# Average over actual sequence lengths.cross_entropy = tf.reduce_sum(cross_entropy, reduction_indices=1) cross_entropy /= tf.cast(self.length, tf.float32)return tf.reduce_mean(cross_entropy) @lazy_propertydef error(self): mistakes = tf.not_equal( tf.argmax(self.target, 2), tf.argmax(self.prediction, 2)) mistakes = tf.cast(mistakes, tf.float32) mask = tf.sign(tf.reduce_max(tf.abs(self.target), reduction_indices=2)) mistakes *= mask# Average over actual sequence lengths.mistakes = tf.reduce_sum(mistakes, reduction_indices=1) mistakes /= tf.cast(self.length, tf.float32)return tf.reduce_mean(mistakes) @lazy_propertydef optimize(self): gradient = self.params.optimizer.compute_gradients(self.cost)try: limit = self.params.gradient_clipping gradient = [ (tf.clip_by_value(g, -limit, limit), v)if g is not None else (None, v)for g, v in gradient]except AttributeError:print('No gradient clipping parameter specified.') optimize = self.params.optimizer.apply_gradients(gradient)return optimize
参考:
「機械知能のための TensorFlow 実践」
WeChat でのコミュニケーションに私を追加してください: qingxingfengzi
私の WeChat 公開アカウント: qingxingfengzigz
私の妻 Zhang Xingqing の WeChat 公開アカウント: qingqingfeifang z
以上がシーケンスアノテーション、手書き小文字OCRデータセット、双方向RNNの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。