Inspect model weights#

By default, VW uses a dense representation for model weights. These weights are accessible from Python in a couple of ways.

Using numpy#

The model weights are exposed (vowpal_wabbit_next.Workspace.weights()) in such a way that Numpy can provide a view into the memory directly. This means there are no memory copies required when inspecting the weights.

Attention

Only dense weights are supported for this method.

There are 3 dimensions to the VW weight array:

  • The feature index

  • The model number (VW supports interleaved models), for most configurations there is only 1 though

  • The weight + extra parameters used for training

    • The 0th item in this dimension is the weight itself and the other values should be seen as an implementation detail.

If we load up a default model a print the shape of the weights it will confirm this:

import vowpal_wabbit_next as vw

workspace = vw.Workspace(["--noconstant"])

weights = workspace.weights()

print(weights.shape)
(262144, 1, 4)

The number of possible feature indices is equal to the value of --num_bits/-b, which is 18 by default. Hence, 2^18 is 262144. There is only 1 model and there are 4 values for each weight. If we pass a few examples to this learner we can inspect the weights learned for each feature.

text_parser = vw.TextFormatParser(workspace)

workspace.learn_one(text_parser.parse_line("1 | a b c"))
workspace.learn_one(text_parser.parse_line("1 | b c d"))
workspace.learn_one(text_parser.parse_line("0.5 | a c"))

weights[workspace.get_index_for_scalar_feature("a")]
array([[0.19704719, 4.0011263 , 1.        , 0.49992964]], dtype=float32)

This shows that a weight of 0.13712466 has been learned for this feature so far. When the model is simple like this we can actually use it to calculate the prediction. Keep in mind we disabled the constant feature earlier, this was to make this calculation easier.

print(
    "using predict_one = {}",
    workspace.predict_one(text_parser.parse_line("| a:0.7 c:0.6")),
)

manual_prediction = (
    0.7 * weights[workspace.get_index_for_scalar_feature("a")][0][0]
    + 0.6 * weights[workspace.get_index_for_scalar_feature("c")][0][0]
)
print("manual prediction = {}", manual_prediction)
using predict_one = {} 0.313994824886322
manual prediction = {} 0.31399484127759936

Using JSON weights#

There is an API (vowpal_wabbit_next.Workspace.json_weights()) which dumps the contents of the model to JSON. This is also useful for debugging, especially since it allows for the feature names to be embedded in the JSON too. Note, this format is still experimental and may change in future.

To ensure that feature names are recorded there are a few rather verbose options that need to be passed.

import vowpal_wabbit_next as vw
import json

workspace = vw.Workspace(record_feature_names=True)

text_parser = vw.TextFormatParser(workspace)

workspace.learn_one(text_parser.parse_line("1 | a b c"))
workspace.learn_one(text_parser.parse_line("1 | b c d"))
workspace.learn_one(text_parser.parse_line("0.5 | a c"))

print(
    json.dumps(json.loads(workspace.json_weights(include_feature_names=True)), indent=4)
)
{
    "weights": [
        {
            "terms": [
                {
                    "name": "d",
                    "namespace": " ",
                    "string_value": null
                }
            ],
            "offset": 0,
            "index": 70771,
            "value": 0.14921310544013977
        },
        {
            "terms": [
                {
                    "name": "a",
                    "namespace": " ",
                    "string_value": null
                }
            ],
            "offset": 0,
            "index": 92594,
            "value": 0.1371246576309204
        },
        {
            "terms": [
                {
                    "name": "Constant",
                    "namespace": "",
                    "string_value": null
                }
            ],
            "offset": 0,
            "index": 116060,
            "value": 0.2089555412530899
        },
        {
            "terms": [
                {
                    "name": "b",
                    "namespace": " ",
                    "string_value": null
                }
            ],
            "offset": 0,
            "index": 163331,
            "value": 0.2274836003780365
        },
        {
            "terms": [
                {
                    "name": "c",
                    "namespace": " ",
                    "string_value": null
                }
            ],
            "offset": 0,
            "index": 185951,
            "value": 0.2089555412530899
        }
    ]
}

In the above we can find “a” and see that its value is the same.