diff --git a/lib/irt_ruby/three_parameter_model.rb b/lib/irt_ruby/three_parameter_model.rb index 309dc31..73eb299 100644 --- a/lib/irt_ruby/three_parameter_model.rb +++ b/lib/irt_ruby/three_parameter_model.rb @@ -5,7 +5,7 @@ module IrtRuby # A class representing the Three-Parameter model for Item Response Theory. class ThreeParameterModel - def initialize(data, max_iter: 1000, tolerance: 1e-6) + def initialize(data, max_iter: 1000, tolerance: 1e-6, learning_rate: 0.01) @data = data @abilities = Array.new(data.row_count) { rand } @difficulties = Array.new(data.column_count) { rand } @@ -13,31 +13,32 @@ def initialize(data, max_iter: 1000, tolerance: 1e-6) @guessings = Array.new(data.column_count) { rand * 0.3 } @max_iter = max_iter @tolerance = tolerance + @learning_rate = learning_rate end + # Sigmoid function to calculate probability def sigmoid(x) 1.0 / (1.0 + Math.exp(-x)) end + # Probability function for the 3PL model def probability(theta, a, b, c) c + (1 - c) * sigmoid(a * (theta - b)) end + # Calculate the log-likelihood of the data given the current parameters def likelihood likelihood = 0 @data.row_vectors.each_with_index do |row, i| row.to_a.each_with_index do |response, j| prob = probability(@abilities[i], @discriminations[j], @difficulties[j], @guessings[j]) - if response == 1 - likelihood += Math.log(prob) - elsif response.zero? - likelihood += Math.log(1 - prob) - end + likelihood += response == 1 ? Math.log(prob) : Math.log(1 - prob) end end likelihood end + # Update parameters using gradient ascent def update_parameters last_likelihood = likelihood @max_iter.times do |_iter| @@ -45,10 +46,11 @@ def update_parameters row.to_a.each_with_index do |response, j| prob = probability(@abilities[i], @discriminations[j], @difficulties[j], @guessings[j]) error = response - prob - @abilities[i] += 0.01 * error * @discriminations[j] - @difficulties[j] -= 0.01 * error * @discriminations[j] - @discriminations[j] += 0.01 * error * (@abilities[i] - @difficulties[j]) - @guessings[j] += 0.01 * error * (1 - prob) + @abilities[i] += @learning_rate * error * @discriminations[j] + @difficulties[j] -= @learning_rate * error * @discriminations[j] + @discriminations[j] += @learning_rate * error * (@abilities[i] - @difficulties[j]) + @guessings[j] += @learning_rate * error * (1 - prob) + @guessings[j] = [[@guessings[j], 0].max, 1].min # Keep guessings within [0, 1] end end current_likelihood = likelihood @@ -58,6 +60,7 @@ def update_parameters end end + # Fit the model to the data def fit update_parameters {