[PYTHON] Examen Mathématiques Partie 4 (Implémentation de l'estimation des paramètres du problème)

Ceci est une suite de Test Mathematics Part 3 (optimisation du modèle 3PL).

La dernière fois, c'était "A propos des mathématiques d'optimisation du modèle 3PL". Cette fois, il s'agit de "A propos de la mise en œuvre du problème d'estimation des paramètres du modèle 3PL".

L'environnement utilisé est

est.

Génération de données

Générer divers paramètres et matrices de réaction d'item pour l'expérience comme effectué dans Test Mathématiques Partie 1 (Définition des questions et génération de données).

import numpy as np
from functools import partial

#Gardez un petit ε pour qu'il ne diverge pas dans le calcul numérique
epsilon =  0.0001
#3 Définition du modèle logistique des paramètres
def L3P(a, b, c, x):
    return c + (1 - epsilon - c) / (1 + np.exp(-  a * (x - b)))

#2 Définition du modèle logistique des paramètres. Afin d'unifier le traitement, c est également pris comme argument.
def L2P(a, b, c, x):
    return (1 - epsilon) / (1 + np.exp(-  a * (x - b)))

#Définition du paramètre du modèle
#a est un nombre réel positif,b est un nombre réel,c doit être supérieur à 0 et inférieur à 1

a_min = 0.7
a_max = 4

b_min = -2
b_max = 2

c_min = 0
c_max = .4

#Combien de questions, combien de personnes, 10 questions 4000 personnes ci-dessous
num_items = 10
num_users = 4_000

#Générer un paramètre de problème
item_params = np.array(
    [np.random.uniform(a_min, a_max, num_items),
     np.random.uniform(b_min, b_max, num_items),
     np.random.uniform(c_min, c_max, num_items)]
).T

#Génération de paramètres candidats
user_params = np.random.normal(size=num_users)

#Créer une matrice de réaction d'élément, élément 1(Bonne réponse)Ou 0(Mauvaise réponse)
#Dans la ligne i et la colonne j, comment le candidat j a-t-il réagi à la question i?
ir_matrix_ij = np.vectorize(int)(
    np.array(
        [partial(L3P, *ip)(user_params) > np.random.uniform(0, 1, num_users) for ip in item_params]
    )
)

Comme précédemment, nous utiliserons $ i $ comme indice pour la question et $ j $ comme indice pour le candidat. Nous porterons un grand nombre de candidats à 1 000 ou plus. En effet, il est empiriquement connu que l'estimation est stable dans une certaine mesure lorsqu'il y a 100 personnes ou plus pour l'estimation 1PL, 300 ou plus pour l'estimation 2PL et 1000 ou plus pour l'estimation 3PL. Si vous êtes intéressé, veuillez changer ici et expérimenter.

Ici, comme paramètre de problème

a /La discrimination b /Difficulté c /Devine
question 1 3.34998814 0.96567682 0.33289520
question 2 1.78741502 1.09887666 0.22340858
question 3 1.33657604 -0.97455532 0.21594273
Question 4 1.05624284 0.84572140 0.11501424
Question 5 1.21345944 1.24370213 0.32661421
Question 6 3.22726757 -0.95479962 0.33023057
Question 7 1.73090248 1.46742090 0.21991115
Question 8 2.16403443 1.66529355 0.10403063
Question 9 2.35283349 1.78746377 0.22301869
Question 10 1.77976105 -0.06035497 0.29241184

Eu Le but est d'estimer cela.

Prétraitement des données

Pour stabiliser la précision de l'estimation avant de faire l'estimation

Est supprimé de la cible d'estimation. En particulier

#Éliminez les problèmes difficiles à estimer.
#Le taux de réponse correct est trop élevé(> 0.9), Trop bas(0.1 <), Il y a trop peu de corrélation avec le score brut(< 0.3)Supprimer le problème
filter_a = ir_matrix_ij.sum(axis=1) / num_users < 0.9
filter_b = ir_matrix_ij.sum(axis=1) / num_users > 0.1
filter_c = np.corrcoef(np.vstack((ir_matrix_ij, ir_matrix_ij.sum(axis=0))))[num_items][:-1] >= 0.3
filter_total = filter_a & filter_b & filter_c

#Redéfinir la matrice de réaction de l'élément
irm_ij = ir_matrix_ij[filter_total]
num_items, num_users = irm_ij.shape

Préparation des fonctions et des constantes pour l'estimation

Dernière fois Comme vous pouvez le voir, chaque problème $ i $

\begin{align}
r_{i\theta} &:= 
\sum_{j}u_{ij} \Pr\{\theta| U_j = u_j, a^{old}, b^{old}, c^{old}\} \\
f_\theta &= 
\sum_{j} \Pr\{\theta| U_j = u_j, a^{old}, b^{old}, c^{old}\} 
\end{align}

Est-ce que l'étape E pour calculer

\begin{align}
R_{i\theta} :=\frac{r_{i\theta}}{P_{i\theta}} - f_\theta
\end{align}

Calculer

\begin{align}
\partial^a\mathcal{\tilde{L}}(a, b, c) 
&= \int R_{i\theta}(\theta - b_i)P_{i\theta}^*d\theta\\
\partial^b\mathcal{\tilde{L}}(a, b, c) 
&= -a_i \int R_{i\theta}P_{i\theta}^*d\theta\\
\partial^c\mathcal{\tilde{L}}(a, b, c) 
&= \frac{1}{(1 - c_i)} \int R_{i\theta} d\theta
\end{align}

Le pas M est de mettre à 0. Ici, $ X_k $ et $ g_k = g (X_k) $ sont préparés comme des points représentatifs de $ \ theta $, car la marginalisation et l'intégration ci-dessus sont résolues d'une manière produit segmenté.

Par conséquent, préparez ce qui suit.

#Définissez la plage possible de paramètres du candidat.
X_k = np.linspace(-4, 4, 41)
#Définissez la distribution des paramètres du candidat. Ici, la distribution normale de scipy est utilisée.
from scipy.stats import norm
g_k = norm.pdf(X_k)
#Fonction étape E
def get_exp_params(irm_ij, g_k, P3_ik):
    Lg_jk = np.exp(irm_ij.T.dot(np.log(P3_ik)) + (1 - irm_ij).T.dot(np.log(1 - P3_ik)))* g_k
    n_Lg_jk = Lg_jk / Lg_jk.sum(axis=1)[:, np.newaxis]
    f_k = n_Lg_jk.sum(axis=0)
    r_ik = irm_ij.dot(n_Lg_jk)
    return f_k, r_ik
#Fonction de score pour l'étape M
def score_(param, f_k, r_k, X_k):
    a, b, c = param
    P3_k = partial(L3P, a, b, c)(X_k)
    P2_k = partial(L2P, a, b, c)(X_k)
    R_k = r_k / P3_k - f_k
    v = [
        ((X_k - b) * R_k * P2_k).sum(),
        - a * (R_k * P2_k).sum(),
        R_k.sum() / (1 - c)
    ]
    return np.linalg.norm(v)

Effectuer une estimation

Maintenant, effectuons l'estimation. Étant donné que l'estimation dépend du paramètre initial et que la stabilité ne devrait pas être aussi bonne, préparez au hasard certains paramètres initiaux et adoptez la valeur médiane parmi les plus stables comme résultat de l'estimation. Pour l'optimisation de l'étape M, nous utiliserons minimiser de scipy.

from scipy.optimize import minimize
#Définissez les contraintes pour minimiser.
cons_ = {
    'type': 'ineq',
    'fun': lambda x:[
        x[0] - a_min,
        a_max - x[0],
        x[1] - b_min,
        b_max - x[1],
        x[2] - c_min,
        c_max - x[2],
    ]
}
#Préparez un paramètre pour la génération initiale des paramètres.
a_min, a_max = 0.1, 8.0
b_min, b_max = -4.0, 4.0
c_min, c_max = epsilon, 0.6

#Oarameter pour l'exécution de l'estimation
#Condition de fin de répétition de l'algorithme EM
delta = 0.001
#Nombre maximum de répétitions de l'algorithme EM
max_num_of_itr = 1000

#Calculer plusieurs fois la stabilité numérique et adopter la valeur médiane parmi les stables
p_data = []
for n_try in range(10):
    #Définissez la valeur initiale de l'estimation.
    item_params_ = np.array(
        [np.random.uniform(a_min, a_max, num_items),
         np.random.uniform(b_min, b_max, num_items),
         np.random.uniform(c_min, c_max, num_items)]
    ).T

    prev_item_params_ = item_params_
    for itr in range(max_num_of_itr):
        # E step :Calcul du paramètre exp
        P3_ik = np.array([partial(L3P, *ip)(X_k) for ip in item_params_])
        f_k, r_ik = get_exp_params(irm_ij, g_k, P3_ik)
        ip_n = []
        #Résoudre les problèmes d'optimisation pour chaque problème
        for item_id in range(num_items):
            target = partial(score_, f_k=f_k, r_k=r_ik[item_id], X_k=X_k)
            result = minimize(target, x0=item_params_[item_id], constraints=cons_, method="slsqp")         
            ip_n.append(list(result.x))

        item_params_ = np.array(ip_n)
        #Le calcul se termine lorsque la différence moyenne par rapport à l'heure précédente tombe en dessous d'une certaine valeur
        mean_diff = abs(prev_item_params_ - item_params_).sum() / item_params_.size
        if mean_diff < delta:
            break
        prev_item_params_ = item_params_

    p_data.append(item_params_)
    
p_data_ = np.array(p_data)
result_ = []
for idx in range(p_data_.shape[1]):
    t_ = np.array(p_data)[:, idx, :]
    #Élimine les extrêmes dans les résultats de calcul
    filter_1 = t_[:, 1] < b_max - epsilon 
    filter_2 = t_[:, 1] > b_min + epsilon
    #La médiane restante est utilisée comme résultat du calcul.
    result_.append(np.median(t_[filter_1 & filter_2], axis=0))
    
result = np.array(result_)

Résultat expérimental

Cette fois, j'ai obtenu ce qui suit à la suite d'un calcul. ** Résultat du calcul (objectif) **

a /La discrimination b /Difficulté c /Devine
question 1 3.49633348(3.34998814) 1.12766137(0.96567682) 0.35744497(0.33289520)
question 2 2.06354365(1.78741502) 1.03621881(1.09887666) 0.20507606(0.22340858)
question 3 1.64406087(1.33657604) -0.39145998(-0.97455532) 0.48094315(0.21594273)
Question 4 1.47999466(1.05624284) 0.95923840(0.84572140) 0.18384673(0.11501424)
Question 5 1.44474336(1.21345944) 1.12406269(1.24370213) 0.31475672(0.32661421)
Question 6 3.91285332(3.22726757) -1.09218709(-0.95479962) 0.18379076(0.33023057)
Question 7 1.44498535(1.73090248) 1.50705016(1.46742090) 0.20601461(0.21991115)
Question 8 2.37497907(2.16403443) 1.61937999(1.66529355) 0.10503096(0.10403063)
Question 9 3.10840278(2.35283349) 1.69962392(1.78746377) 0.22051818(0.22301869)
Question 10 1.79969976(1.77976105) 0.06053145(-0.06035497) 0.29944448(0.29241184)

Il y a des valeurs étranges telles que b (difficulté) en Q3, mais elles peuvent être estimées avec une bonne précision.

la prochaine fois

La prochaine fois, nous évaluerons le paramètre de capacité $ \ theta_j $ du candidat en utilisant le résultat du paramètre en question. Examen Mathématiques Partie 5 (Estimation du paramètre de l'examinateur)

Les références

Recommended Posts

Examen Mathématiques Partie 4 (Implémentation de l'estimation des paramètres du problème)
Examen Mathématiques Partie 5 (Estimation des paramètres des candidats)
Test mathématique 2 (modèle mathématique de la théorie de la réaction des items)