Understand scalar value representation in Tensors
masterTensor<T, N>::maximum() or the inner product of two 1D tensors via contractions return a 0-rank tensor (Tensor<T, 0>).repository·master·Indexed 43 days ago
https://github.com/cahlr/pybktA Python implementation of Bayesian Knowledge Tracing (BKT) and its variants used to estimate student cognitive mastery from problem-solving sequences. It features a pure Python version and a high-performance C++ extension for efficient model fitting and prediction. The library includes a Model class for training and evaluation (similar to SciKit-Learn), a Roster class for simulating learning environments, and support for various BKT model variants including individual student and item priors, learn rates, guess, and slip parameters.
Tensor<T, N>::maximum() or the inner product of two 1D tensors via contractions return a 0-rank tensor (Tensor<T, 0>).The pyBKT model requires a specific data structure to model student mastery. Mastery is treated as a latent variable with two states: "knowing" and "not knowing".
num_subparts: Number of unique questions used for checking understanding.num_resources: Number of unique learning resources available.num_fit_initialization: Number of iterations in the EM step.Data Object Attributesdata: A matrix of sequential checkpoints. Rows = subparts, Columns = student checkpoints.0: No response or no question asked.1: Wrong response.2: Correct response.starts: Defines each student's starting column index in the data matrix.lengths: Defines the number of checkpoints for each student.resources: Sequential IDs of resources at each checkpoint (matches the length of the data columns).stateseqs: The true knowledge state (leave undefined before running the model).fitmodel attributes)As: Transition probabilities between states (includes learns and forgets).learns: Probability of transitioning to "knowing" from "not knowing".forgets: Probability of transitioning to "not knowing" from "knowing".prior: Prior probability of "knowing".guesses: Probability of guessing correctly while "not knowing".slips: Probability of an incorrect response while "knowing".Eigen Tensors support two layouts: ColMajor (the default) and RowMajor.
Important Constraints:
ColMajor is currently fully supported; using RowMajor is not recommended.To change the layout of a tensor, use the swap_layout() method. Note that swap_layout() reverses the order of the dimensions. To swap the layout while preserving the original dimension order, combine swap_layout() with a shuffle() operation.
// Explicitly specifying layouts
Tensor<float, 3, ColMajor> col_major;
TensorMap<Tensor<float, 3, RowMajor>> row_major(data, ...);
// Simple layout swap (reverses dimension order)
Tensor<float, 2, ColMajor> col_major_orig(2, 4);
Tensor<float, 2, RowMajor> row_major_swapped = col_major_orig.swap_layout();
// col_major_orig (2,4) becomes row_major_swapped (4,2)
// Swap layout while preserving dimension order
array<int, 2> shuffle(1, 0);
Tensor<float, 2, RowMajor> row_major_orig(2, 4);
Tensor<float, 2, ColMajor> col_major_preserved = row_major_orig.swap_layout().shuffle(shuffle);
// row_major_orig (2,4) becomes col_major_preserved (2,4)Because Eigen uses lazy evaluation, using the auto keyword with tensor operations will capture the expression tree (the operator object) rather than the resulting tensor.
Warning: You cannot access elements directly from an auto expression. Attempting to do so will result in a compilation error.
To get the actual values, you must assign the expression to a concrete Tensor type.
Tensor<float, 3> t3 = t1 + t2;
cout << t3(0, 0, 0); // OK: prints the sum
auto t4 = t1 + t2;
cout << t4(0, 0, 0); // ERROR: Compilation error! t4 is an operator, not a tensorWhen working with Eigen Tensors, the documentation uses several pseudo-types to describe dimensions and elements:
<Tensor-Type>::Dimensions: An array-like object of ints representing the tensor's dimensions. It has a .size attribute (the rank) and can be indexed like an array.<Tensor-Type>::Index: An integer type used for indexing tensors along their dimensions.<Tensor-Type>::Scalar: The underlying data type of the individual tensor elements (e.g., float for Tensor<float, 2>).<Operation>: Indicates that a method returns a deferred operation rather than a concrete tensor. Operations must be evaluated (e.g., by assigning them to a tensor) before values can be accessed.pyBKT supports various Bayesian Knowledge Tracing (BKT) model variants from literature, allowing you to model different levels of individualization:
The Roster class simulates a cohort of students. It tracks individual progress by storing only the current latent and observable states, making it highly efficient for simulating learning environments.
Key Operations:
Roster(students=[...], skills=..., model=...): Initializes a roster for specific students and skills.update_state(skill, student, responses): Updates a student's state using a single response or an array of responses (e.g., np.ones(10) for 10 correct responses).get_state_type(skill, student): Returns the mastery state (e.g., StateType.MASTERED).get_mastery_prob(skill, student): Returns the probability of mastery.get_correct_probs(skill) / get_mastery_probs(skill): Returns aggregate statistics for the whole roster.add_student(skill, name): Adds a new student to a skill.remove_student(skill, name): Removes a student.reset_state(skill, student): Resets a student to their initial prior state.from pyBKT.models import *
import numpy as np
model = Model()
model.fit(data_path='ct.csv')
# Initialize roster
roster = Roster(students=['Jeff', 'Bob'], skills='Calculate unit rate', model=model)
# Update state with 10 correct responses
roster.update_state('Calculate unit rate', 'Jeff', np.ones(10))
# Check mastery
if roster.get_state_type('Calculate unit rate', 'Jeff') == StateType.MASTERED:
print("Jeff has mastered the skill!")
# Get aggregate probabilities
print(roster.get_correct_probs('Calculate unit rate'))Eigen Tensor operations return non-evaluated Operations objects rather than immediate results. These operations can be chained together (e.g., a.constant(2.0f).pow(2.0f)). The entire chain is evaluated lazily, typically only when the result is assigned to a Tensor object. This allows the library to optimize the evaluation process.
// Operations are lazy and can be chained
Eigen::Tensor<float, 2> a(2, 3);
a.setConstant(1.0f);
// The expression below is not evaluated until assigned to 'c'
Eigen::Tensor<float, 2> c = (a + a.constant(2.0f)) * a.constant(0.2f);pyBKT accepts the following input formats:
Correctness values: The correct column must use -1 for no response, 0 for incorrect, and 1 for correct.
Column Mapping: Because column names vary by dataset, you must ensure pyBKT can map your columns to its expected internal names (e.g., order_id, skill_name, correct). For standard datasets like Cognitive Tutor or Assistments, mapping is often automatic. For custom datasets, use a defaults dictionary.
Eigen Tensor operations use lazy evaluation. When you perform an operation like t1 + t2, the library does not immediately compute the sum. Instead, it constructs a lightweight "tensor operator" object (e.g., TensorCwiseBinaryOp) that holds references to the operands.
Actual computation only occurs when the expression is assigned to a concrete tensor type (like Tensor, TensorFixedSize, or TensorMap) or when an element is accessed via a TensorRef.
This mechanism allows the library to optimize complex expression trees and avoid unnecessary intermediate allocations.
Tensor<float, 3> t1(2, 3, 4);
Tensor<float, 3> t2(2, 3, 4);
// t3 is the result of the actual addition performed during assignment
Tensor<float, 3> t3 = t1 + t2;You can force the evaluation of a lazy expression using several methods:
Tensor, TensorFixedSize, or TensorMap triggers evaluation..eval(): Inserts an intermediate evaluation point in an expression tree. This is useful for improving performance in complex expressions or preventing aliasing issues (where the output tensor is also an input).TensorRef: A wrapper that allows accessing individual elements of an expression by evaluating them on-the-fly. Use this for accessing subsets of values without materializing a full tensor.When to use .eval():
reshape() or broadcast()).Y = Y / Y.sum()).// 1. Evaluation via assignment
auto t3 = t1 + t2;
Tensor<float, 3> result = t3; // Evaluated here
// 2. Evaluation via .eval() to prevent aliasing
Y = Y / (Y.sum(depth_dim).eval().reshape(dims2d).broadcast(bcast));
// 3. Evaluation via TensorRef for on-the-fly access
TensorRef<Tensor<float, 3>> ref = ((t1 + t2) * 0.2f).exp();
float val = ref(0, 0, 0); // Evaluated on demandYou can install pyBKT using pip. By default, pip attempts to install the optimized Python/C++ extension version. If the required C++ libraries are missing or installation fails, it will automatically fall back to the pure Python implementation.
If pip fails, you can install manually from the repository source:
pip install pyBKT# Manual installation if pip fails
git clone https://github.com/CAHLR/pyBKT.git
cd pyBKT
python3 setup.py install