<jupyter_start><jupyter_text>Features<jupyter_code>def word2features(sent, i):
    word = sent[i][0]
    postag = sent[i][1]

    features = {
        'bias': 1.0,
        'word.lower()': word.lower(),
        'word[-3:]': word[-3:],
        'word.isupper()': word.isupper(),
        'word.istitle()': word.istitle(),
        'word.isdigit()': word.isdigit(),
        'postag': postag,
        'postag[:2]': postag[:2],
    }
    if i > 0:
        word1 = sent[i-1][0]
        postag1 = sent[i-1][1]
        features.update({
            '-1:word.lower()': word1.lower(),
            '-1:word.istitle()': word1.istitle(),
            '-1:word.isupper()': word1.isupper(),
            '-1:postag': postag1,
            '-1:postag[:2]': postag1[:2],
        })
    else:
        features['BOS'] = True

    if i < len(sent)-1:
        word1 = sent[i+1][0]
        postag1 = sent[i+1][1]
        features.update({
            '+1:word.lower()': word1.lower(),
            '+1:word.istitle()': word1.istitle(),
            '+1:word.isupper()': word1.isupper(),
            '+1:postag': postag1,
            '+1:postag[:2]': postag1[:2],
        })
    else:
        features['EOS'] = True

    return features

def sent2features(sent):
    return [word2features(sent, i) for i in range(len(sent))]

def sent2labels(sent):
    return [label for token, postag, label in sent]

def sent2tokens(sent):
    return [token for token, postag, label in sent]
sent2features(train_sents[0])[0]<jupyter_output><empty_output><jupyter_text>Extract features from the data.<jupyter_code>%time
X_train = [sent2features(s) for s in train_sents]
y_train = [sent2labels(s) for s in train_sents]

X_test = [sent2features(s) for s in test_sents]
y_test = [sent2labels(s) for s in test_sents]
X_train[0][0:2]
y_train[0][0:2]
X_train[2][0:2]
y_train[2][0:2]<jupyter_output><empty_output><jupyter_text>Training<jupyter_code>%%time
crf = sklearn_crfsuite.CRF(
    algorithm='lbfgs',
    c1=0.1,
    c2=0.1,
    max_iterations=30,
    all_possible_transitions=False,
)
crf.fit(X_train, y_train)<jupyter_output>CPU times: user 18.4 s, sys: 68 ms, total: 18.5 s
Wall time: 18.5 s<jupyter_text>Evaluation<jupyter_code>labels = list(crf.classes_)
labels.remove('O')
labels
y_pred = crf.predict(X_test)
metrics.flat_f1_score(y_test, y_pred, average='weighted', labels=labels)
# group B and I results
sorted_labels = sorted(
    labels, 
    key=lambda name: (name[1:], name[0])
)
print(metrics.flat_classification_report(
    y_test, y_pred, labels=sorted_labels, digits=3
))<jupyter_output>precision    recall  f1-score   support

      B-LOC      0.610     0.643     0.626      1084
      I-LOC      0.295     0.369     0.328       325
     B-MISC      0.230     0.145     0.178       339
     I-MISC      0.259     0.404     0.315       557
      B-ORG      0.721     0.642     0.679      1400
      I-ORG      0.713     0.426     0.533      1104
      B-PER      0.697     0.774     0.734       735
      I-PER      0.737     0.886     0.805       634

avg / total      0.608     0.581     0.584      6178<jupyter_text>Older tests<jupyter_code>eli5.show_weights(crf, top=30)
crf = sklearn_crfsuite.CRF(
    algorithm='lbfgs',
    c1=0.1,
    c2=0.1,
    max_iterations=20,
    all_possible_transitions=True,
)
crf.fit(X_train, y_train);
eli5.show_weights(crf, top=5, show=['transition_features'])
eli5.show_weights(crf, top=10, targets=['O', 'B-ORG', 'I-ORG'])
eli5.show_weights(crf, top=10, feature_re='^word\.is',
                  horizontal_layout=False, show=['targets'])
expl = eli5.explain_weights(crf, top=5, targets=['O', 'B-LOC', 'I-LOC'])
print(eli5.format_as_text(expl))<jupyter_output>Explained as: CRF

Transition features:
            O    B-LOC    I-LOC
-----  ------  -------  -------
O       2.732    1.217   -4.675
B-LOC  -0.226   -0.091    3.378
I-LOC  -0.184   -0.585    2.404

y='O' top features
Weight  Feature       
------  --------------
+4.931  BOS           
+3.754  postag[:2]:Fp 
+3.539  bias          
… 15043 more positive …
… 3906 more negative …
-3.685  word.isupper()
-7.025  word.istitle()

y='B-LOC' top features
Weight  Feature           
------  ------------------
+2.397  word.istitle()    
+2.147  -1:word.lower():en
  … 2284 more positive …  
  … 433 more negative …   
-1.080  postag:SP         
-1.080  postag[:2]:SP     
-1.273  -1:word.istitle() 

y='I-LOC' top features
Weight  Feature           
------  ------------------
+0.882  -1:word.lower():de
+0.780  -1:word.istitle() 
+0.718  word[-3:]:de      
+0.711  word.lower():de   
  … 1684 more positive …  
  … 268 more negative …   
-1.965  BOS
{'fraction_non_alphanumeric': 0.17237640936686904, 'fraction_numerical': 0.0826105810928014, 'mean_word_length': 2.253173483779972, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '496186', 'license': 'MIT', 'n_tokens_mistral': 1974, 'n_tokens_neox': 1801, 'n_words': 354, 'path': 'crf-python/crf-tutorial.ipynb', 'repo_name': 'plopezgarcia/one-offs', 'size': '4612'}