def model_fn(features, labels, mode, params):
    '\n\n    :param features: words from sentence and number of words per sentence\n    :param labels: One tag per word\n    :param mode:  tf.estimator.ModeKeys.TRAIN or  tf.estimator.ModeKeys.PREDICT or  tf.estimator.ModeKeys.EVAL\n    :param params: dictionary of hyper parameters for the model\n    :return:\n    '
    if isinstance(features, dict):
        features = (features['words'], features['nwords'])
    (words, nwords) = features
    training = (mode == tf.estimator.ModeKeys.TRAIN)
    vocab_words = tf.contrib.lookup.index_table_from_file(params['vocab_words_file'], num_oov_buckets=params['num_oov_buckets'])
    '\n    If the file contains the following: \n    B-LOC\n    B-PER\n    O\n    I-LOC\n    \n    then indices = [0, 1, 3] and num_tags = 4\n    \n    Open Question: The special treatment of tag indices is probably needed for microavg metrics. Why though?\n    '
    with Path(params['vocab_tags_file']).open('r') as f:
        indices = [idx for (idx, tag) in enumerate(f) if (tag.strip() != 'O')]
        num_tags = (len(indices) + 1)
    word_ids = vocab_words.lookup(words)
    glove = np.load(params['glove'])['embeddings']
    glove = np.vstack([glove, [([0.0] * params['dim'])]])
    variable = tf.Variable(glove, dtype=tf.float32, trainable=False)
    embeddings = tf.nn.embedding_lookup(variable, word_ids)
    dropout = params['dropout']
    embeddings = tf.layers.dropout(embeddings, rate=dropout, training=training)
    time_major = tf.transpose(embeddings, perm=[1, 0, 2])
    lstm_cell_fw = tf.contrib.rnn.LSTMBlockFusedCell(params['lstm_size'])
    lstm_cell_bw = tf.contrib.rnn.LSTMBlockFusedCell(params['lstm_size'])
    lstm_cell_bw = tf.contrib.rnn.TimeReversedFusedRNN(lstm_cell_bw)
    '\n    Any LSTM Cell returns two things: Cell Output (h) and Cell State (c)\n\n    Following this, lstm_fw or lstm_bw each return a pair containing:\n\n    Cell Output: A 3-D tensor of shape [time_len, batch_size, output_size]\n    Final state: a tuple (cell_state, output) produced by the last LSTM Cell in the sequence.\n\n    '
    (output_fw, _) = lstm_cell_fw(time_major, dtype=tf.float32, sequence_length=nwords)
    (output_bw, _) = lstm_cell_bw(time_major, dtype=tf.float32, sequence_length=nwords)
    output = tf.concat([output_fw, output_bw], axis=(- 1))
    output = tf.transpose(output, perm=[1, 0, 2])
    output = tf.layers.dropout(output, rate=dropout, training=training)
    logits = tf.layers.dense(output, num_tags)
    crf_params = tf.get_variable('crf', shape=[num_tags, num_tags], dtype=tf.float32)
    (pred_ids, _) = tf.contrib.crf.crf_decode(logits, crf_params, nwords)
    if (mode == tf.estimator.ModeKeys.PREDICT):
        reverse_vocab_tags = tf.contrib.lookup.index_to_string_table_from_file(params['vocab_tags_file'])
        pred_strings = reverse_vocab_tags.lookup(tf.to_int64(pred_ids))
        predictions = {'pred_ids': pred_ids, 'tags': pred_strings}
        return tf.estimator.EstimatorSpec(mode, predictions=predictions)
    vocab_tags = tf.contrib.lookup.index_table_from_file(params['vocab_tags_file'])
    label_ids = vocab_tags.lookup(labels)
    '\n    logits are the same thing as unary potentials,\n    checkout https://guillaumegenthial.github.io/sequence-tagging-with-tensorflow.html look for scores s[i]\n    '
    (log_likelihood, _) = tf.contrib.crf.crf_log_likelihood(logits, label_ids, nwords, crf_params)
    loss = tf.reduce_mean((- log_likelihood))
    weights = tf.sequence_mask(nwords)
    metrics = {'acc': tf.metrics.accuracy(label_ids, pred_ids, weights), 'precision': precision(label_ids, pred_ids, num_tags, indices, weights), 'recall': recall(label_ids, pred_ids, num_tags, indices, weights), 'f1': f1(label_ids, pred_ids, num_tags, indices, weights)}
    for (metric_name, op) in metrics.items():
        tf.summary.scalar(metric_name, op[1])
    if (mode == tf.estimator.ModeKeys.EVAL):
        return tf.estimator.EstimatorSpec(mode, loss=loss, eval_metric_ops=metrics)
    elif (mode == tf.estimator.ModeKeys.TRAIN):
        train_op = tf.train.AdamOptimizer().minimize(loss, global_step=tf.train.get_or_create_global_step())
        return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)
:param features: words from sentence and number of words per sentence
:param labels: One tag per word
:param mode:  tf.estimator.ModeKeys.TRAIN or  tf.estimator.ModeKeys.PREDICT or  tf.estimator.ModeKeys.EVAL
:param params: dictionary of hyper parameters for the model
:return:
def model_fn(features, labels, mode, params):
    '\n\n    :param features: words from sentence and number of words per sentence\n    :param labels: One tag per word\n    :param mode:  tf.estimator.ModeKeys.TRAIN or  tf.estimator.ModeKeys.PREDICT or  tf.estimator.ModeKeys.EVAL\n    :param params: dictionary of hyper parameters for the model\n    :return:\n    '
    if isinstance(features, dict):
        features = (features['words'], features['nwords'])
    (words, nwords) = features
    training = (mode == tf.estimator.ModeKeys.TRAIN)
    vocab_words = tf.contrib.lookup.index_table_from_file(params['vocab_words_file'], num_oov_buckets=params['num_oov_buckets'])
    '\n    If the file contains the following: \n    B-LOC\n    B-PER\n    O\n    I-LOC\n    \n    then indices = [0, 1, 3] and num_tags = 4\n    \n    Open Question: The special treatment of tag indices is probably needed for microavg metrics. Why though?\n    '
    with Path(params['vocab_tags_file']).open('r') as f:
        indices = [idx for (idx, tag) in enumerate(f) if (tag.strip() != 'O')]
        num_tags = (len(indices) + 1)
    word_ids = vocab_words.lookup(words)
    glove = np.load(params['glove'])['embeddings']
    glove = np.vstack([glove, [([0.0] * params['dim'])]])
    variable = tf.Variable(glove, dtype=tf.float32, trainable=False)
    embeddings = tf.nn.embedding_lookup(variable, word_ids)
    dropout = params['dropout']
    embeddings = tf.layers.dropout(embeddings, rate=dropout, training=training)
    time_major = tf.transpose(embeddings, perm=[1, 0, 2])
    lstm_cell_fw = tf.contrib.rnn.LSTMBlockFusedCell(params['lstm_size'])
    lstm_cell_bw = tf.contrib.rnn.LSTMBlockFusedCell(params['lstm_size'])
    lstm_cell_bw = tf.contrib.rnn.TimeReversedFusedRNN(lstm_cell_bw)
    '\n    Any LSTM Cell returns two things: Cell Output (h) and Cell State (c)\n\n    Following this, lstm_fw or lstm_bw each return a pair containing:\n\n    Cell Output: A 3-D tensor of shape [time_len, batch_size, output_size]\n    Final state: a tuple (cell_state, output) produced by the last LSTM Cell in the sequence.\n\n    '
    (output_fw, _) = lstm_cell_fw(time_major, dtype=tf.float32, sequence_length=nwords)
    (output_bw, _) = lstm_cell_bw(time_major, dtype=tf.float32, sequence_length=nwords)
    output = tf.concat([output_fw, output_bw], axis=(- 1))
    output = tf.transpose(output, perm=[1, 0, 2])
    output = tf.layers.dropout(output, rate=dropout, training=training)
    logits = tf.layers.dense(output, num_tags)
    crf_params = tf.get_variable('crf', shape=[num_tags, num_tags], dtype=tf.float32)
    (pred_ids, _) = tf.contrib.crf.crf_decode(logits, crf_params, nwords)
    if (mode == tf.estimator.ModeKeys.PREDICT):
        reverse_vocab_tags = tf.contrib.lookup.index_to_string_table_from_file(params['vocab_tags_file'])
        pred_strings = reverse_vocab_tags.lookup(tf.to_int64(pred_ids))
        predictions = {'pred_ids': pred_ids, 'tags': pred_strings}
        return tf.estimator.EstimatorSpec(mode, predictions=predictions)
    vocab_tags = tf.contrib.lookup.index_table_from_file(params['vocab_tags_file'])
    label_ids = vocab_tags.lookup(labels)
    '\n    logits are the same thing as unary potentials,\n    checkout https://guillaumegenthial.github.io/sequence-tagging-with-tensorflow.html look for scores s[i]\n    '
    (log_likelihood, _) = tf.contrib.crf.crf_log_likelihood(logits, label_ids, nwords, crf_params)
    loss = tf.reduce_mean((- log_likelihood))
    weights = tf.sequence_mask(nwords)
    metrics = {'acc': tf.metrics.accuracy(label_ids, pred_ids, weights), 'precision': precision(label_ids, pred_ids, num_tags, indices, weights), 'recall': recall(label_ids, pred_ids, num_tags, indices, weights), 'f1': f1(label_ids, pred_ids, num_tags, indices, weights)}
    for (metric_name, op) in metrics.items():
        tf.summary.scalar(metric_name, op[1])
    if (mode == tf.estimator.ModeKeys.EVAL):
        return tf.estimator.EstimatorSpec(mode, loss=loss, eval_metric_ops=metrics)
    elif (mode == tf.estimator.ModeKeys.TRAIN):
        train_op = tf.train.AdamOptimizer().minimize(loss, global_step=tf.train.get_or_create_global_step())
        return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)