├── .idea
├── Knowledge_Tracing.iml
├── dictionaries
│ └── linglingsu.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── Paper
├── Addressing Two Problems in Deep Knowledge Tracing via Prediction-Consistent Regularization.pdf
├── Deep Factorization Machines for Knowledge Tracing.pdf
├── Exercise-Enhanced Sequential Modeling for Student Performance Prediction.pdf
├── Going Deeper with Deep Knowledge Tracing .pdf
├── How Deep is Knowledge Tracing?.pdf
├── Incorporating rich features into Deep knowledge tracing.pdf
├── Knowledge Tracing Machines- Factorization Machines for Knowledge Tracing.pdf
├── Knowledge tracing Modeling the acquisition of procedural knowledge.pdf
└── deep Knowledge Tracing.pdf
├── README.md
├── __init__.py
└── code
├── DKT+
├── __init__.py
├── metrics.py
├── model.py
├── model_params.py
├── run_dkt.py
└── utils.py
├── DKT
├── __init__.py
├── metrics.py
├── model.py
├── model_params.py
├── run_dkt.py
└── utils.py
├── TCN-KT
├── __init__.py
├── metrics.py
├── model.py
├── model_params.py
├── modules.py
├── run_dkt.py
└── utils.py
├── Transformer-KT
├── __init__.py
├── metrics.py
├── model.py
├── model_params.py
├── modules.py
├── run_dkt.py
└── utils.py
└── data
├── 2016-EDM
├── 0910_a_test.csv
├── 0910_a_train.csv
├── 0910_b_test.csv
├── 0910_b_train.csv
├── 0910_c_test.csv
├── 0910_c_train.csv
├── 2015_builder_test.csv
├── 2015_builder_train.csv
├── CAT_test.csv
└── CAT_train.csv
├── Assistment
└── 2009-2010_skill_builder_data.csv
├── csv2tfrecords.py
├── eval.tfrecord
└── train.tfrecord
/.idea/Knowledge_Tracing.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/dictionaries/linglingsu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Paper/Addressing Two Problems in Deep Knowledge Tracing via Prediction-Consistent Regularization.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sulingling123/Knowledge_Tracing/e47425c068066190c0a435dc1ba1ce2c2a9c56ec/Paper/Addressing Two Problems in Deep Knowledge Tracing via Prediction-Consistent Regularization.pdf
--------------------------------------------------------------------------------
/Paper/Deep Factorization Machines for Knowledge Tracing.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sulingling123/Knowledge_Tracing/e47425c068066190c0a435dc1ba1ce2c2a9c56ec/Paper/Deep Factorization Machines for Knowledge Tracing.pdf
--------------------------------------------------------------------------------
/Paper/Exercise-Enhanced Sequential Modeling for Student Performance Prediction.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sulingling123/Knowledge_Tracing/e47425c068066190c0a435dc1ba1ce2c2a9c56ec/Paper/Exercise-Enhanced Sequential Modeling for Student Performance Prediction.pdf
--------------------------------------------------------------------------------
/Paper/Going Deeper with Deep Knowledge Tracing .pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sulingling123/Knowledge_Tracing/e47425c068066190c0a435dc1ba1ce2c2a9c56ec/Paper/Going Deeper with Deep Knowledge Tracing .pdf
--------------------------------------------------------------------------------
/Paper/How Deep is Knowledge Tracing?.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sulingling123/Knowledge_Tracing/e47425c068066190c0a435dc1ba1ce2c2a9c56ec/Paper/How Deep is Knowledge Tracing?.pdf
--------------------------------------------------------------------------------
/Paper/Incorporating rich features into Deep knowledge tracing.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sulingling123/Knowledge_Tracing/e47425c068066190c0a435dc1ba1ce2c2a9c56ec/Paper/Incorporating rich features into Deep knowledge tracing.pdf
--------------------------------------------------------------------------------
/Paper/Knowledge Tracing Machines- Factorization Machines for Knowledge Tracing.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sulingling123/Knowledge_Tracing/e47425c068066190c0a435dc1ba1ce2c2a9c56ec/Paper/Knowledge Tracing Machines- Factorization Machines for Knowledge Tracing.pdf
--------------------------------------------------------------------------------
/Paper/Knowledge tracing Modeling the acquisition of procedural knowledge.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sulingling123/Knowledge_Tracing/e47425c068066190c0a435dc1ba1ce2c2a9c56ec/Paper/Knowledge tracing Modeling the acquisition of procedural knowledge.pdf
--------------------------------------------------------------------------------
/Paper/deep Knowledge Tracing.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sulingling123/Knowledge_Tracing/e47425c068066190c0a435dc1ba1ce2c2a9c56ec/Paper/deep Knowledge Tracing.pdf
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Knowledge_Tracing
2 | - Paper : 知识追踪相关论文
3 | - [Deep Knowledge Tracing](https://github.com/ZoeYuhan/Knowledge_Tracing/blob/master/Paper/deep%20Knowledge%20Tracing.pdf):
4 | - 首次提出将RNN用于知识追踪,并能够基于复杂的知识联系进行建模(如构建知识图谱)
5 | - [How Deep is Knowledge Tracing](https://github.com/ZoeYuhan/Knowledge_Tracing/blob/master/Paper/How%20Deep%20is%20Knowledge%20Tracing%3F.pdf)
6 | - 探究DKT利用到的统计规律并拓展BKT,从而使BKT拥有能够与DKT相匹配的能力
7 | - [Going Deeper with Deep Knowledge Tracing](https://github.com/ZoeYuhan/Knowledge_Tracing/blob/master/Paper/Going%20Deeper%20with%20Deep%20Knowledge%20Tracing%20.pdf)
8 | - 对DKT和PFA,BKT进行了模型比较,对DKT模型能碾压其他两种模型的结果进行了怀疑并加以论证,进一步讨论了原论文能够得出上述结果的原因,对进一步使用DKT模型提供了参考。
9 | - [Incorporating Rich Features Into Deep Knowledge Tracing](https://github.com/ZoeYuhan/Knowledge_Tracing/blob/master/Paper/Incorporating%20rich%20features%20into%20Deep%20knowledge%20tracing.pdf)
10 | - 对DKT使用上进行数据层扩展,扩展学生和问题层的数据输入,包括结合自动编码器对输入进行转换
11 | - [Addressing Two Problems in Deep Knowledge Tracing viaPrediction-Consistent Regularization](https://github.com/ZoeYuhan/Knowledge_Tracing/blob/master/Paper/Addressing%20Two%20Problems%20in%20Deep%20Knowledge%20Tracing%20via%20Prediction-Consistent%20Regularization.pdf)
12 | - 指出DKT模型现存缺点:对输入序列存在重构问题和预测结果的波动性,进而对上述问题提出了改善方法
13 | - [Exercise-Enhanced Sequential Modeling for Student Performance Prediction](https://github.com/ZoeYuhan/Knowledge_Tracing/blob/master/Paper/Exercise-Enhanced%20Sequential%20Modeling%20for%20Student%20Performance%20Prediction.pdf)
14 | - 将题面信息引入,不仅作为输入送入模型,而且将题目编码后的向量计算cosine相似度作为atention的socre
15 | - [A Self-Attentive model for Knowledge Tracing](https://arxiv.org/pdf/1907.06837.pdf)
16 | - 使用Transformer应用于知识追踪
17 | ## Method
18 | | model | paper |
19 | | ---- | ---- |
20 | | DKT | [Deep Knowledge Tracing](https://github.com/ZoeYuhan/Knowledge_Tracing/blob/master/Paper/deep%20Knowledge%20Tracing.pdf) |
21 | | DKT+ | [Addressing Two Problems in Deep Knowledge Tracing viaPrediction-Consistent Regularization](https://github.com/ZoeYuhan/Knowledge_Tracing/blob/master/Paper/Addressing%20Two%20Problems%20in%20Deep%20Knowledge%20Tracing%20via%20Prediction-Consistent%20Regularization.pdf) |
22 | | TCN-KT| None |
23 | | Transformer-KT | [A Self-Attentive model for Knowledge Tracing](https://arxiv.org/pdf/1907.06837.pdf) |
24 |
25 |
26 |
27 | ## Usage :
28 |
29 | ```bash
30 | python DKT/run_dkt.py
31 | ```
32 |
33 | ----
34 | ### Acknowledgement
35 | - Blog:
36 | - [深度知识追踪](https://sulingling123.github.io/2019/08/06/%E6%B7%B1%E5%BA%A6%E7%9F%A5%E8%AF%86%E8%BF%BD%E8%B8%AA/)
37 | - [论文导读:Exercise-Enhanced Sequential Modeling for Student Performance Prediction](https://blog.csdn.net/Zoe_Su/article/details/84566409)
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:__init__.py.py
5 | @time:2019/2/19下午12:41
6 | """
--------------------------------------------------------------------------------
/code/DKT+/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:__init__.py.py
5 | @time:2019/10/811:10 AM
6 | """
--------------------------------------------------------------------------------
/code/DKT+/metrics.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:metrics.py
5 | @time:2019/8/122:57 PM
6 | """
7 | import tensorflow as tf
8 | from model_params import *
9 | from model import DKT
10 |
11 |
12 | def dkt_loss(logits, target_correct, target_ids, correct, ids, seq_steps):
13 | """
14 | calculate cross-entropy loss for dkt
15 | Args:
16 | logits: logits(no sigmoid func) with shape [batch_size, length, vocab_size]
17 | target_correct: targets with shape [batch_size, length]
18 | target_ids:
19 | seq_steps:
20 |
21 | Returns:
22 | cross-entropy loss
23 |
24 | """
25 | with tf.name_scope('loss'):
26 | batch_size, length, vocab_size = tf.shape(logits)[0], tf.shape(logits)[1], tf.shape(logits)[2]
27 | flat_logits = tf.reshape(logits, [-1])
28 | flat_target_correct = tf.reshape(target_correct, [-1])
29 |
30 | flat_correctness = tf.reshape(correct, [-1])
31 |
32 | flat_base_target_index = tf.range(batch_size * length) * vocab_size
33 | flat_bias_target_id = tf.reshape(target_ids, [-1])
34 | flat_target_id = flat_base_target_index + flat_bias_target_id
35 |
36 | flat_target_logits = tf.gather(flat_logits, flat_target_id)
37 | mask = tf.reshape(tf.sequence_mask(seq_steps, maxlen=length), [-1])
38 | # drop predict which user not react to
39 | flat_target_correct_mask = tf.boolean_mask(flat_target_correct, mask)
40 | flat_target_logits_mask = tf.boolean_mask(flat_target_logits, mask)
41 |
42 | loss = tf.reduce_mean(tf.nn.weighted_cross_entropy_with_logits(
43 | targets=flat_target_correct_mask,
44 | logits=flat_target_logits_mask,
45 | pos_weight=1
46 | ))
47 |
48 | r = tf.reduce_mean(tf.nn.weighted_cross_entropy_with_logits(targets=flat_correctness,
49 | logits=flat_target_logits,
50 | pos_weight=1))
51 | pred_all = tf.sigmoid(logits)
52 | w1 = tf.reduce_mean(tf.abs(pred_all[:, :-1, :] - pred_all[:, 1:, :]))
53 | w2 = tf.reduce_mean(tf.square(pred_all[:, :-1, :] - pred_all[:, 1:, :]))
54 | loss += 0.5 * r + 0.1 * w1 + 0.1 * w2
55 | return loss
56 |
57 |
58 | def padded_accuracy(logits, target, target_ids, seq_len):
59 | """
60 | Percentage of times that predictions matches albels on non-0s
61 | Args:
62 | logits: Tensor with shape [batch_size, length, vocab_size]
63 | target: Tesnor wtth shape [batch_size, length]
64 |
65 | Returns:
66 |
67 | """
68 | with tf.variable_scope('padded_accuracy', values=[logits, target, target_ids, seq_len]):
69 | batch_size, length, vocab_size = tf.shape(logits)[0], tf.shape(logits)[1], tf.shape(logits)[2]
70 | flat_logits = tf.reshape(logits, [-1])
71 | flat_target_correct = tf.reshape(target, [-1])
72 |
73 | flat_base_target_index = tf.range(batch_size * length) * vocab_size
74 | flat_bias_target_id = tf.reshape(target_ids, [-1])
75 | flat_target_id = flat_base_target_index + flat_bias_target_id
76 |
77 | flat_target_logits = tf.gather(flat_logits, flat_target_id)
78 | pred = tf.sigmoid(tf.reshape(flat_target_logits, [batch_size, length]))
79 | # self.binary_pred = tf.cast(tf.greater_equal(self.pred, 0.5), tf.int32)
80 | binary_pred = tf.cast(tf.greater(pred, 0.5), tf.float32)
81 |
82 | predict = tf.reshape(binary_pred, [-1])
83 | mask = tf.reshape(tf.sequence_mask(seq_len, maxlen=tf.shape(logits)[1]), [-1])
84 | flat_predict_mask = tf.boolean_mask(predict, mask)
85 | flat_target_mask = tf.boolean_mask(flat_target_correct, mask)
86 | return tf.equal(flat_target_mask, flat_predict_mask)
87 |
88 |
89 | def _convert_to_eval_metric(metric_fn):
90 | """
91 | warper a metric_fn that returns scores and weights as an eval metric fn
92 | The input metric_fn returns values for the current batch.
93 | The wrapper aggregates the return values collected over all of the batches evaluated
94 | Args:
95 | metric_fn: function that return socres for current batch's logits and targets
96 |
97 | Returns:
98 | function that aggregates the score and weights from metric_fn
99 |
100 | """
101 | def problem_metric_fn(*args):
102 | """
103 | return an aggregation of the metric_fn's returned values
104 | Args:
105 | *args:
106 |
107 | Returns:
108 |
109 | """
110 | score = metric_fn(*args)
111 | return tf.metrics.mean(score)
112 | return problem_metric_fn
113 |
114 |
115 | def get_eval_metrics(logits, labels, target_ids, seq_len):
116 | """
117 | return dictionary of model evaluation metrics
118 | Args:
119 | logits:
120 | labels:
121 | params:
122 |
123 | Returns:
124 |
125 | """
126 | metrics = {
127 | 'accuracy': _convert_to_eval_metric(padded_accuracy)(logits, labels, target_ids, seq_len),
128 | }
129 | return metrics
--------------------------------------------------------------------------------
/code/DKT+/model.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:model.py
5 | @time:2019/10/83:13 PM
6 | """
7 | from __future__ import absolute_import
8 | from __future__ import division
9 | from __future__ import print_function
10 |
11 | import tensorflow as tf
12 |
13 |
14 | class DKT(object):
15 | def __init__(self, params, train):
16 | """
17 | Initialize layers to build DKT model
18 | Args:
19 | params: hyperparameter object defining layer size, dropout value etc
20 | train: boolean indicating whether the model is in training mode
21 | """
22 | self.params = params
23 | self.train = train
24 |
25 | def __call__(self, batch_size, inputs, seq_len):
26 | initial_state = []
27 | if self.params['use_one_hot']:
28 | self.embedding_output = tf.one_hot(inputs, depth=2*self.params['num_skills'] + 1)
29 | else: # Use Embedding
30 | embedding_table = tf.get_variable(
31 | name="word_embedding",
32 | shape=[2*self.params['num_skills']+1, self.params['hidden_size']],
33 | initializer=tf.truncated_normal_initializer(self.params['initializer_range']))
34 | self.embedding_output = tf.nn.embedding_lookup(embedding_table, inputs)
35 | self.embedding_output = tf.contrib.layers.layer_norm(self.embedding_output,
36 | begin_norm_axis=-1,
37 | begin_params_axis=-1, scope='embedding_ln')
38 | self.embedding_output = tf.nn.dropout(self.embedding_output, keep_prob=self.params['keep_prob'])
39 |
40 | hidden_layers = []
41 | final_hidden_size = self.params['hidden_size']
42 | with tf.variable_scope('lstm_cell', initializer=tf.orthogonal_initializer()):
43 | for i in range(self.params['hidden_layer_num']):
44 | if self.params['use_LN']:
45 | hidden_layer = tf.contrib.rnn.LayerNormBasicLSTMCell(num_units=final_hidden_size,
46 | forget_bias=0.0,
47 | layer_norm=True,
48 | dropout_keep_prob=self.params['keep_prob'])
49 | else:
50 | hidden_layer = tf.contrib.rnn.BasicLSTMCell(num_units=final_hidden_size, forget_bias=0,
51 | state_is_tuple=True)
52 | # lstm_layer = tf.contrib.rnn.GRUCell(num_units=final_hidden_size, state_is_tuple=True)
53 |
54 | hidden_layer = tf.contrib.rnn.DropoutWrapper(cell=hidden_layer,
55 | output_keep_prob=self.params['keep_prob'])
56 | hidden_layers.append(hidden_layer)
57 | initial_state.append(hidden_layer.zero_state(batch_size, dtype=tf.float32))
58 | self.hidden_cell = tf.contrib.rnn.MultiRNNCell(cells=hidden_layers, state_is_tuple=True)
59 |
60 | # dynamic rnn
61 | state_series, self.current_state = tf.nn.dynamic_rnn(cell=self.hidden_cell,
62 | inputs=self.embedding_output,
63 | sequence_length=seq_len,
64 | initial_state= tuple(initial_state))
65 |
66 | output_w = tf.get_variable("weights", [final_hidden_size, self.params['num_skills']],
67 | initializer=tf.truncated_normal_initializer(self.params['initializer_range']),
68 | # regularizer=tf.contrib.layers.l2_regularizer(self.config.regularization_lambda)
69 | regularizer=None
70 | )
71 |
72 | output_b = tf.get_variable("biases", [self.params['num_skills']],
73 | initializer=tf.zeros_initializer(),
74 | # regularizer=tf.contrib.layers.l2_regularizer(self.config.regularization_lambda)
75 | regularizer=None
76 | )
77 |
78 | batch_size, max_len = tf.shape(inputs)[0], tf.shape(inputs)[1]
79 | self.state_series = tf.reshape(state_series, [batch_size * max_len, final_hidden_size])
80 |
81 | logits = tf.matmul(self.state_series, output_w) + output_b
82 | logits = tf.reshape(logits, [batch_size, max_len, self.params['num_skills']])
83 | # self.pred_all = tf.sigmoid(self.mat_logits)
84 |
85 | return logits
--------------------------------------------------------------------------------
/code/DKT+/model_params.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:model_params.py
5 | @time:2019/10/83:31 PM
6 | """
7 | from collections import defaultdict
8 |
9 |
10 | BASE_PARAMS = defaultdict(
11 | # Input params
12 | num_skills=125,
13 |
14 | # Model params
15 | initializer_range=0.02, # embedding table initializer
16 | keep_prob=0.5,
17 | use_LN=True,
18 | hidden_layer_num=4,
19 | hidden_size=128,
20 | use_one_hot=False,
21 |
22 | # Training params
23 | learning_rate=0.1,
24 | learning_rate_decay_rate=1.0,
25 | learning_rate_warmup_steps=16000,
26 |
27 | # Optimizer params
28 | optimizer_adam_beta1=0.9,
29 | optimizer_adam_beta2=0.997,
30 | optimizer_adam_epsilon=1e-09,
31 |
32 | )
--------------------------------------------------------------------------------
/code/DKT+/run_dkt.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:run_dkt.py
5 | @time:2019/8/1212:39 PM
6 | """
7 | from __future__ import absolute_import
8 | from __future__ import division
9 | from __future__ import print_function
10 |
11 | import tensorflow as tf
12 | import os
13 | import numpy as np
14 | from model_params import BASE_PARAMS
15 | from model import *
16 | from metrics import *
17 | from utils import *
18 |
19 |
20 |
21 | flags = tf.flags
22 | logging = tf.logging
23 |
24 | flags.DEFINE_string(name='train_data', default='../data/train.tfrecord',
25 | help='Path of training data ')
26 | flags.DEFINE_string(name='valid_data', default='../data/eval.tfrecord',
27 | help='Path of valid data')
28 | flags.DEFINE_string(name='predict_data', default='',
29 | help='Path of predict data')
30 | flags.DEFINE_string(name='saved_model', default='./saved_model', help='Path to save model')
31 | flags.DEFINE_string(name='output_dir', default='./output_dir', help='Path to save model')
32 | flags.DEFINE_integer(name='epoch', default=100, help='Num of epoch')
33 | flags.DEFINE_integer(name='batch_size', default=1, help='Num of batch')
34 | flags.DEFINE_bool(name="do_train", default=True, help="Whether to run training.")
35 | flags.DEFINE_bool(name="do_eval", default=True, help="Whether to run eval on the dev set.")
36 | flags.DEFINE_bool(
37 | "do_predict", False,
38 | "Whether to run the model in inference mode on the test set.")
39 | flags.DEFINE_bool(name='pb', default=True, help='')
40 | FLAGS = flags.FLAGS
41 |
42 |
43 | def model_fn_builder():
44 | def model_fn(features, labels, mode, params):
45 | """
46 | define how to train, evaluate and predict from the transfomer model.
47 | Args:
48 |
49 | mode:
50 | params:
51 |
52 | Returns:
53 |
54 | """
55 | inputs = features['inputs']
56 | seq_steps = features['seq_len']
57 |
58 |
59 | is_training = (mode == tf.estimator.ModeKeys.TRAIN)
60 |
61 | try:
62 | batch_size, length = get_shape_list(inputs, expected_rank=2)
63 | except ValueError:
64 | batch_size = 1
65 | length = get_shape_list(inputs, expected_rank=1)[0]
66 | inputs = tf.reshape(inputs, [batch_size, length])
67 |
68 | with tf.variable_scope('model'):
69 | # Build model
70 | model = DKT(params, is_training)
71 | logits = model(batch_size, inputs, seq_steps) # [batch, length, vocab_size]
72 |
73 | # when in prediction mode, the label/target is Bone, the model output is the prediction
74 | if mode == tf.estimator.ModeKeys.PREDICT:
75 | export_outputs = {'predict_output': tf.estimator.export.PredictOutput({"predict": tf.sigmoid(logits)})}
76 | output_spec = tf.estimator.EstimatorSpec(mode=mode,
77 | predictions={'predict': tf.sigmoid(logits)},
78 | export_outputs=export_outputs
79 | )
80 | else:
81 | # Calculate model loss
82 | target_ids = features['target_id']
83 | target_correct = features['target_correct']
84 | ids = features['ids']
85 | correct = features['correct']
86 | loss = dkt_loss(logits, target_correct, target_ids, correct, ids, seq_steps)
87 | record_dict = {}
88 | record_dict['minibatch_loss'] = loss
89 | # Save loss as named tensor will be logged with the logging hook
90 | tf.identity(loss, 'cross_entropy')
91 |
92 | if mode == tf.estimator.ModeKeys.EVAL:
93 | metric_dict = get_eval_metrics(logits, target_correct, target_ids, seq_steps)
94 | record_dict['accuracy'] = metric_dict['accuracy']
95 | record_scalars(record_dict)
96 | output_spec = tf.estimator.EstimatorSpec(mode=tf.estimator.ModeKeys.EVAL,
97 | loss=loss,
98 | predictions={'predict': tf.sigmoid(logits)},
99 | eval_metric_ops=metric_dict )
100 | else: # train
101 | # check whether restore from checkpoint
102 | tvars = tf.trainable_variables()
103 | initialized_variable_names = {}
104 |
105 | tf.logging.info("**** Trainable Variables ****")
106 | for var in tvars:
107 | init_string = ""
108 | if var.name in initialized_variable_names:
109 | init_string = ", *INIT_FROM_CKPT*"
110 | tf.logging.info(" name = %s, shape = %s%s", var.name, var.shape,
111 | init_string)
112 |
113 | train_op, metric_dict = get_train_op_and_metrics(loss, params)
114 | acc_metric = get_eval_metrics(logits, target_correct, target_ids, seq_steps)
115 | record_dict['accuracy'] = acc_metric['accuracy']
116 | record_dict['learning_rate'] = metric_dict['learning_rate']
117 | record_scalars(record_dict)
118 | output_spec = tf.estimator.EstimatorSpec(mode=tf.estimator.ModeKeys.TRAIN,
119 | loss=loss,
120 | train_op=train_op
121 | )
122 | return output_spec
123 | return model_fn
124 |
125 |
126 | def input_fn_builder(datadir, epoch):
127 | def input_fn():
128 | padded_shapes = {'inputs': [None],
129 | 'target_correct': [None],
130 | 'target_id': [None],
131 | 'ids': [None],
132 | 'correct': [None],
133 |
134 | 'seq_len': [],
135 | }
136 |
137 | dataset_train = get_dataset(datadir)
138 | dataset_train = dataset_train.repeat(epoch).shuffle(1000).padded_batch(FLAGS.batch_size,
139 | padded_shapes=padded_shapes)
140 |
141 | return dataset_train
142 |
143 | return input_fn
144 |
145 |
146 | def main():
147 | # if not FLAGS.do_train and not FLAGS.do_eval and not FLAGS.do_predict:
148 | # raise ValueError("At least one of `do_train`, `do_eval` or `do_predict' must be True.")
149 | params = BASE_PARAMS # param dict from model_params.py
150 | tf.gfile.MakeDirs(FLAGS.saved_model)
151 |
152 | run_config = tf.estimator.RunConfig(
153 | model_dir=FLAGS.saved_model,
154 | tf_random_seed=tf.set_random_seed([42]),
155 | # save_summary_step=100,
156 | save_checkpoints_steps=1000,
157 | # save_checkpoints_secs=600,
158 | session_config=None,
159 | keep_checkpoint_max=5,
160 | keep_checkpoint_every_n_hours=10000,
161 | log_step_count_steps=100,
162 | train_distribute=None
163 | )
164 | model_fn = model_fn_builder()
165 | estimator = tf.estimator.Estimator(
166 | model_fn=model_fn,
167 | model_dir=FLAGS.saved_model,
168 | config=run_config,
169 | params=params,
170 | warm_start_from=None
171 | )
172 | # train_input_fn = input_fn_builder(FLAGS.train_data, FLAGS.epoch)
173 | # train_spec = tf.estimator.TrainSpec(input_fn=train_input_fn,
174 | # max_steps=None)
175 | # eval_input_fn = input_fn_builder(FLAGS.valid_data, 1)
176 | # eval_spec = tf.estimator.EvalSpec(input_fn=eval_input_fn,
177 | # steps=None,
178 | # throttle_secs=60)
179 | # tf.estimator.train_and_evaluate(estimator,
180 | # train_spec=train_spec,
181 | # eval_spec=eval_spec)
182 | for epoch in range(FLAGS.epoch):
183 | if FLAGS.do_train:
184 | train_input_fn = input_fn_builder(FLAGS.train_data, 1)
185 |
186 | estimator.train(input_fn=train_input_fn)
187 | if FLAGS.do_eval:
188 | eval_input_fn = input_fn_builder(FLAGS.valid_data, 1)
189 | result = estimator.evaluate(input_fn=eval_input_fn)
190 |
191 | output_eval_file = os.path.join(FLAGS.output_dir, "eval_results.txt")
192 | with tf.gfile.GFile(output_eval_file, "w+") as writer:
193 | tf.logging.info("***** Eval results *****")
194 | for key in sorted(result.keys()):
195 | tf.logging.info(" %s = %s", key, str(result[key]))
196 | writer.write("%s = %s\n" % (key, str(result[key])))
197 |
198 |
199 | if __name__ == '__main__':
200 | tf.logging.set_verbosity(tf.logging.INFO)
201 | # flags.mark_flag_as_required("train_data")
202 | # flags.mark_flag_as_required("valid_data")
203 | # flags.mark_flag_as_required("saved_model")
204 |
205 | main()
206 |
--------------------------------------------------------------------------------
/code/DKT+/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:utils.py
5 | @time:2019/8/125:04 PM
6 | """
7 | from __future__ import absolute_import
8 | from __future__ import division
9 | from __future__ import print_function
10 |
11 | import tensorflow as tf
12 | import collections
13 | import six
14 | import re
15 |
16 | def assert_rank(tensor, expected_rank, name=None):
17 | """Raises an exception if the tensor rank is not of the expected rank.
18 |
19 | Args:
20 | tensor: A tf.Tensor to check the rank of.
21 | expected_rank: Python integer or list of integers, expected rank.
22 | name: Optional name of the tensor for the error message.
23 |
24 | Raises:
25 | ValueError: If the expected shape doesn't match the actual shape.
26 | """
27 | if name is None:
28 | name = tensor.name
29 |
30 | expected_rank_dict = {}
31 | if isinstance(expected_rank, six.integer_types):
32 | expected_rank_dict[expected_rank] = True
33 | else:
34 | for x in expected_rank:
35 | expected_rank_dict[x] = True
36 |
37 | actual_rank = tensor.shape.ndims
38 | if actual_rank not in expected_rank_dict:
39 | scope_name = tf.get_variable_scope().name
40 | raise ValueError(
41 | "For the tensor `%s` in scope `%s`, the actual rank "
42 | "`%d` (shape = %s) is not equal to the expected rank `%s`" %
43 | (name, scope_name, actual_rank, str(tensor.shape), str(expected_rank)))
44 |
45 |
46 | def get_shape_list(tensor, expected_rank=None, name=None):
47 | """Returns a list of the shape of tensor, preferring static dimensions.
48 |
49 | Args:
50 | tensor: A tf.Tensor object to find the shape of.
51 | expected_rank: (optional) int. The expected rank of `tensor`. If this is
52 | specified and the `tensor` has a different rank, and exception will be
53 | thrown.
54 | name: Optional name of the tensor for the error message.
55 |
56 | Returns:
57 | A list of dimensions of the shape of tensor. All static dimensions will
58 | be returned as python integers, and dynamic dimensions will be returned
59 | as tf.Tensor scalars.
60 | """
61 | if name is None:
62 | name = tensor.name
63 |
64 | if expected_rank is not None:
65 | assert_rank(tensor, expected_rank, name)
66 |
67 | shape = tensor.shape.as_list()
68 |
69 | non_static_indexes = []
70 | for (index, dim) in enumerate(shape):
71 | if dim is None:
72 | non_static_indexes.append(index)
73 |
74 | if not non_static_indexes:
75 | return shape
76 |
77 | dyn_shape = tf.shape(tensor)
78 | for index in non_static_indexes:
79 | shape[index] = dyn_shape[index]
80 | return shape
81 |
82 |
83 | def get_learning_rate(learning_rate, hidden_size, learning_rate_warmup_steps):
84 | """calculate learning rate with linear warmup and rsqrt decay"""
85 | with tf.name_scope('learning_rate'):
86 | warmup_steps = tf.to_float(learning_rate_warmup_steps)
87 | step = tf.to_float(tf.train.get_or_create_global_step())
88 |
89 | learning_rate *= (hidden_size ** -0.5)
90 | # Apply linear warmup
91 | learning_rate *= tf.minimum(1.0, step/warmup_steps)
92 | # Apply rsqrt decay
93 | learning_rate *= tf.rsqrt(tf.maximum(step, warmup_steps))
94 |
95 | tf.identity(learning_rate, "learning_rate")
96 | return learning_rate
97 |
98 |
99 | def get_train_op_and_metrics(loss, params):
100 | """Generate training op and metrics to save in tensorboard"""
101 | with tf.variable_scope('get_train_op'):
102 | learning_rate = get_learning_rate(
103 | learning_rate=params['learning_rate'],
104 | hidden_size=params['hidden_size'],
105 | learning_rate_warmup_steps=params['learning_rate_warmup_steps']
106 | )
107 | # create optimizer , Use lazyAdamOptimizer from TF contrib,which is faster than the TF core Adam operation
108 | optimizer = tf.contrib.opt.LazyAdamOptimizer(
109 | learning_rate=learning_rate,
110 | beta1=params['optimizer_adam_beta1'],
111 | beta2=params['optimizer_adam_beta2'],
112 | epsilon=params['optimizer_adam_epsilon']
113 | )
114 | # calculate and apply graph gradient using LazyAdamOptimizer
115 | global_step = tf.train.get_global_step()
116 | tvars = tf.trainable_variables()
117 | gradients = optimizer.compute_gradients(loss, tvars, colocate_gradients_with_ops=True)
118 | minimize_op = optimizer.apply_gradients(gradients,
119 | global_step=global_step,
120 | name='train')
121 | update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
122 | train_op = tf.group(minimize_op, update_ops)
123 |
124 | train_metrics = {'learning_rate': learning_rate,
125 | 'global_step': global_step}
126 | return train_op, train_metrics
127 |
128 |
129 | def record_scalars(metric_dict):
130 | for key, value in metric_dict.items():
131 | print('records_scalars', key)
132 | if key == 'accuracy':
133 | tf.summary.scalar(name=key, tensor=value[1])
134 | else:
135 | tf.summary.scalar(name=key, tensor=value)
136 |
137 |
138 | def get_assignment_map_from_checkpoint(tvars, init_checkpoint):
139 | """Compute the union of the current variables and checkpoint variables."""
140 | assignment_map = {}
141 | initialized_variable_names = {}
142 |
143 | name_to_variable = collections.OrderedDict()
144 | for var in tvars:
145 | name = var.name
146 | m = re.match("^(.*):\\d+$", name)
147 | if m is not None:
148 | name = m.group(1)
149 | name_to_variable[name] = var
150 |
151 | init_vars = tf.train.list_variables(init_checkpoint)
152 |
153 | assignment_map = collections.OrderedDict()
154 | for x in init_vars:
155 | (name, var) = (x[0], x[1])
156 | if name not in name_to_variable:
157 | continue
158 | assignment_map[name] = name
159 | initialized_variable_names[name] = 1
160 | initialized_variable_names[name + ":0"] = 1
161 |
162 | return (assignment_map, initialized_variable_names)
163 |
164 |
165 | def parse_exmp(serial_exmp):
166 | feats = tf.parse_single_example(serial_exmp, features={'inputs': tf.VarLenFeature(tf.float32),
167 | 'target_correct': tf.VarLenFeature(tf.int64),
168 | 'target_id': tf.VarLenFeature(tf.float32),
169 |
170 | 'correct': tf.VarLenFeature(tf.int64),
171 | 'ids': tf.VarLenFeature(tf.float32),
172 |
173 | 'seq_len': tf.FixedLenFeature([], tf.int64)})
174 | inputs = tf.sparse_tensor_to_dense(feats['inputs']) # 使用VarLenFeature读入的是一个sparse_tensor,用该函数进行转换
175 | target_correct = tf.sparse_tensor_to_dense(feats['target_correct'])
176 | target_id = tf.sparse_tensor_to_dense(feats['target_id'])
177 |
178 | correct = tf.sparse_tensor_to_dense(feats['correct'])
179 | id = tf.sparse_tensor_to_dense(feats['ids'])
180 |
181 | inputs = tf.cast(inputs, tf.int32)
182 | target_correct = tf.cast(target_correct, tf.float32)
183 | target_id = tf.cast(target_id, tf.int32)
184 |
185 | correct = tf.cast(correct, tf.float32)
186 | id = tf.cast(id, tf.int32)
187 |
188 | seq_len = tf.cast(feats['seq_len'], tf.int32)
189 |
190 | return {'inputs': inputs,
191 | 'target_correct': target_correct,
192 | 'target_id': target_id,
193 | 'ids': id,
194 | 'correct': correct,
195 | 'seq_len': seq_len}
196 |
197 |
198 | def get_dataset(fname):
199 | dataset = tf.data.TFRecordDataset(fname)
200 | return dataset.map(parse_exmp) # use padded_batch method if padding needed
201 |
--------------------------------------------------------------------------------
/code/DKT/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:__init__.py.py
5 | @time:2019/10/811:10 AM
6 | """
--------------------------------------------------------------------------------
/code/DKT/metrics.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:metrics.py
5 | @time:2019/8/122:57 PM
6 | """
7 | import tensorflow as tf
8 | from model_params import *
9 | from model import DKT
10 |
11 |
12 | def dkt_loss(logits, target_correct, target_ids, seq_steps):
13 | """
14 | calculate cross-entropy loss for dkt
15 | Args:
16 | logits: logits(no sigmoid func) with shape [batch_size, length, vocab_size]
17 | target_correct: targets with shape [batch_size, length]
18 | target_ids:
19 | seq_steps:
20 |
21 | Returns:
22 | cross-entropy loss
23 |
24 | """
25 | with tf.name_scope('loss'):
26 | batch_size, length, vocab_size = tf.shape(logits)[0], tf.shape(logits)[1], tf.shape(logits)[2]
27 | flat_logits = tf.reshape(logits, [-1])
28 | flat_target_correct = tf.reshape(target_correct, [-1])
29 |
30 | flat_base_target_index = tf.range(batch_size * length) * vocab_size
31 | flat_bias_target_id = tf.reshape(target_ids, [-1])
32 | flat_target_id = flat_base_target_index + flat_bias_target_id
33 |
34 | flat_target_logits = tf.gather(flat_logits, flat_target_id)
35 | mask = tf.reshape(tf.sequence_mask(seq_steps, maxlen=length), [-1])
36 | # drop predict which user not react to
37 | flat_target_correct_mask = tf.boolean_mask(flat_target_correct, mask)
38 | flat_target_logits_mask = tf.boolean_mask(flat_target_logits, mask)
39 |
40 | loss = tf.reduce_mean(tf.nn.weighted_cross_entropy_with_logits(
41 | targets=flat_target_correct_mask,
42 | logits=flat_target_logits_mask,
43 | pos_weight=1
44 | ))
45 | return loss
46 |
47 |
48 | def padded_accuracy(logits, target, target_ids, seq_len):
49 | """
50 | Percentage of times that predictions matches albels on non-0s
51 | Args:
52 | logits: Tensor with shape [batch_size, length, vocab_size]
53 | target: Tesnor wtth shape [batch_size, length]
54 |
55 | Returns:
56 |
57 | """
58 | with tf.variable_scope('padded_accuracy', values=[logits, target, target_ids, seq_len]):
59 | batch_size, length, vocab_size = tf.shape(logits)[0], tf.shape(logits)[1], tf.shape(logits)[2]
60 | flat_logits = tf.reshape(logits, [-1])
61 | flat_target_correct = tf.reshape(target, [-1])
62 |
63 | flat_base_target_index = tf.range(batch_size * length) * vocab_size
64 | flat_bias_target_id = tf.reshape(target_ids, [-1])
65 | flat_target_id = flat_base_target_index + flat_bias_target_id
66 |
67 | flat_target_logits = tf.gather(flat_logits, flat_target_id)
68 | pred = tf.sigmoid(tf.reshape(flat_target_logits, [batch_size, length]))
69 | # self.binary_pred = tf.cast(tf.greater_equal(self.pred, 0.5), tf.int32)
70 | binary_pred = tf.cast(tf.greater(pred, 0.5), tf.float32)
71 |
72 | predict = tf.reshape(binary_pred, [-1])
73 | mask = tf.reshape(tf.sequence_mask(seq_len, maxlen=tf.shape(logits)[1]), [-1])
74 | flat_predict_mask = tf.boolean_mask(predict, mask)
75 | flat_target_mask = tf.boolean_mask(flat_target_correct, mask)
76 | return tf.equal(flat_target_mask, flat_predict_mask)
77 |
78 |
79 | def _convert_to_eval_metric(metric_fn):
80 | """
81 | warper a metric_fn that returns scores and weights as an eval metric fn
82 | The input metric_fn returns values for the current batch.
83 | The wrapper aggregates the return values collected over all of the batches evaluated
84 | Args:
85 | metric_fn: function that return socres for current batch's logits and targets
86 |
87 | Returns:
88 | function that aggregates the score and weights from metric_fn
89 |
90 | """
91 | def problem_metric_fn(*args):
92 | """
93 | return an aggregation of the metric_fn's returned values
94 | Args:
95 | *args:
96 |
97 | Returns:
98 |
99 | """
100 | score = metric_fn(*args)
101 | return tf.metrics.mean(score)
102 | return problem_metric_fn
103 |
104 |
105 | def get_eval_metrics(logits, labels, target_ids, seq_len):
106 | """
107 | return dictionary of model evaluation metrics
108 | Args:
109 | logits:
110 | labels:
111 | params:
112 |
113 | Returns:
114 |
115 | """
116 | metrics = {
117 | 'accuracy': _convert_to_eval_metric(padded_accuracy)(logits, labels, target_ids, seq_len),
118 | }
119 | return metrics
--------------------------------------------------------------------------------
/code/DKT/model.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:model.py
5 | @time:2019/10/83:13 PM
6 | """
7 | from __future__ import absolute_import
8 | from __future__ import division
9 | from __future__ import print_function
10 |
11 | import tensorflow as tf
12 |
13 |
14 | class DKT(object):
15 | def __init__(self, params, train):
16 | """
17 | Initialize layers to build DKT model
18 | Args:
19 | params: hyperparameter object defining layer size, dropout value etc
20 | train: boolean indicating whether the model is in training mode
21 | """
22 | self.params = params
23 | self.train = train
24 |
25 | def __call__(self, batch_size, inputs, seq_len):
26 | initial_state = []
27 | if self.params['use_one_hot']:
28 | self.embedding_output = tf.one_hot(inputs, depth=2*self.params['num_skills'] + 1)
29 | else: # Use Embedding
30 | embedding_table = tf.get_variable(
31 | name="word_embedding",
32 | shape=[2*self.params['num_skills']+1, self.params['hidden_size']],
33 | initializer=tf.truncated_normal_initializer(self.params['initializer_range']))
34 | self.embedding_output = tf.nn.embedding_lookup(embedding_table, inputs)
35 | self.embedding_output = tf.contrib.layers.layer_norm(self.embedding_output,
36 | begin_norm_axis=-1,
37 | begin_params_axis=-1, scope='embedding_ln')
38 | self.embedding_output = tf.nn.dropout(self.embedding_output, keep_prob=self.params['keep_prob'])
39 |
40 | hidden_layers = []
41 | final_hidden_size = self.params['hidden_size']
42 | with tf.variable_scope('lstm_cell', initializer=tf.orthogonal_initializer()):
43 | for i in range(self.params['hidden_layer_num']):
44 | if self.params['use_LN']:
45 | hidden_layer = tf.contrib.rnn.LayerNormBasicLSTMCell(num_units=final_hidden_size,
46 | forget_bias=0.0,
47 | layer_norm=True,
48 | dropout_keep_prob=self.params['keep_prob'])
49 | else:
50 | hidden_layer = tf.contrib.rnn.BasicLSTMCell(num_units=final_hidden_size, forget_bias=0,
51 | state_is_tuple=True)
52 | # lstm_layer = tf.contrib.rnn.GRUCell(num_units=final_hidden_size, state_is_tuple=True)
53 |
54 | hidden_layer = tf.contrib.rnn.DropoutWrapper(cell=hidden_layer,
55 | output_keep_prob=self.params['keep_prob'])
56 | hidden_layers.append(hidden_layer)
57 | initial_state.append(hidden_layer.zero_state(batch_size, dtype=tf.float32))
58 | self.hidden_cell = tf.contrib.rnn.MultiRNNCell(cells=hidden_layers, state_is_tuple=True)
59 |
60 | # dynamic rnn
61 | state_series, self.current_state = tf.nn.dynamic_rnn(cell=self.hidden_cell,
62 | inputs=self.embedding_output,
63 | sequence_length=seq_len,
64 | initial_state= tuple(initial_state))
65 |
66 | output_w = tf.get_variable("weights", [final_hidden_size, self.params['num_skills']],
67 | initializer=tf.truncated_normal_initializer(self.params['initializer_range']),
68 | # regularizer=tf.contrib.layers.l2_regularizer(self.config.regularization_lambda)
69 | regularizer=None
70 | )
71 |
72 | output_b = tf.get_variable("biases", [self.params['num_skills']],
73 | initializer=tf.zeros_initializer(),
74 | # regularizer=tf.contrib.layers.l2_regularizer(self.config.regularization_lambda)
75 | regularizer=None
76 | )
77 |
78 | batch_size, max_len = tf.shape(inputs)[0], tf.shape(inputs)[1]
79 | self.state_series = tf.reshape(state_series, [batch_size * max_len, final_hidden_size])
80 |
81 | logits = tf.matmul(self.state_series, output_w) + output_b
82 | logits = tf.reshape(logits, [batch_size, max_len, self.params['num_skills']])
83 | # self.pred_all = tf.sigmoid(self.mat_logits)
84 |
85 | return logits
--------------------------------------------------------------------------------
/code/DKT/model_params.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:model_params.py
5 | @time:2019/10/83:31 PM
6 | """
7 | from collections import defaultdict
8 |
9 |
10 | BASE_PARAMS = defaultdict(
11 | # Input params
12 | num_skills=125,
13 |
14 | # Model params
15 | initializer_range=0.02, # embedding table initializer
16 | keep_prob=0.5,
17 | use_LN=True,
18 | hidden_layer_num=4,
19 | hidden_size=128,
20 | use_one_hot=False,
21 |
22 | # Training params
23 | learning_rate=0.1,
24 | learning_rate_decay_rate=1.0,
25 | learning_rate_warmup_steps=16000,
26 |
27 | # Optimizer params
28 | optimizer_adam_beta1=0.9,
29 | optimizer_adam_beta2=0.997,
30 | optimizer_adam_epsilon=1e-09,
31 |
32 | )
--------------------------------------------------------------------------------
/code/DKT/run_dkt.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:run_dkt.py
5 | @time:2019/8/1212:39 PM
6 | """
7 | from __future__ import absolute_import
8 | from __future__ import division
9 | from __future__ import print_function
10 |
11 | import tensorflow as tf
12 | import os
13 | import numpy as np
14 | from model_params import BASE_PARAMS
15 | from model import *
16 | from metrics import *
17 | from utils import *
18 |
19 |
20 |
21 | flags = tf.flags
22 | logging = tf.logging
23 |
24 | flags.DEFINE_string(name='train_data', default='../data/train.tfrecord',
25 | help='Path of training data ')
26 | flags.DEFINE_string(name='valid_data', default='../data/eval.tfrecord',
27 | help='Path of valid data')
28 | flags.DEFINE_string(name='predict_data', default='',
29 | help='Path of predict data')
30 | flags.DEFINE_string(name='saved_model', default='./saved_model', help='Path to save model')
31 | flags.DEFINE_string(name='output_dir', default='./output_dir', help='Path to save model')
32 | flags.DEFINE_integer(name='epoch', default=100, help='Num of epoch')
33 | flags.DEFINE_integer(name='batch_size', default=1, help='Num of batch')
34 | flags.DEFINE_bool(name="do_train", default=True, help="Whether to run training.")
35 | flags.DEFINE_bool(name="do_eval", default=True, help="Whether to run eval on the dev set.")
36 | flags.DEFINE_bool(
37 | "do_predict", False,
38 | "Whether to run the model in inference mode on the test set.")
39 | flags.DEFINE_bool(name='pb', default=True, help='')
40 | FLAGS = flags.FLAGS
41 |
42 |
43 | def model_fn_builder():
44 | def model_fn(features, labels, mode, params):
45 | """
46 | define how to train, evaluate and predict from the transfomer model.
47 | Args:
48 |
49 | mode:
50 | params:
51 |
52 | Returns:
53 |
54 | """
55 | inputs = features['inputs']
56 | seq_steps = features['seq_len']
57 |
58 |
59 | is_training = (mode == tf.estimator.ModeKeys.TRAIN)
60 |
61 | try:
62 | batch_size, length = get_shape_list(inputs, expected_rank=2)
63 | except ValueError:
64 | batch_size = 1
65 | length = get_shape_list(inputs, expected_rank=1)[0]
66 | inputs = tf.reshape(inputs, [batch_size, length])
67 |
68 | with tf.variable_scope('model'):
69 | # Build model
70 | model = DKT(params, is_training)
71 | logits = model(batch_size, inputs, seq_steps) # [batch, length, vocab_size]
72 |
73 | # when in prediction mode, the label/target is Bone, the model output is the prediction
74 | if mode == tf.estimator.ModeKeys.PREDICT:
75 | export_outputs = {'predict_output': tf.estimator.export.PredictOutput({"predict": tf.sigmoid(logits)})}
76 | output_spec = tf.estimator.EstimatorSpec(mode=mode,
77 | predictions={'predict': tf.sigmoid(logits)},
78 | export_outputs=export_outputs
79 | )
80 | else:
81 | # Calculate model loss
82 | target_ids = features['target_id']
83 | target_correct = features['target_correct']
84 |
85 | loss = dkt_loss(logits, target_correct, target_ids, seq_steps)
86 | record_dict = {}
87 | record_dict['minibatch_loss'] = loss
88 | # Save loss as named tensor will be logged with the logging hook
89 | tf.identity(loss, 'cross_entropy')
90 |
91 | if mode == tf.estimator.ModeKeys.EVAL:
92 | metric_dict = get_eval_metrics(logits, target_correct, target_ids, seq_steps)
93 | record_dict['accuracy'] = metric_dict['accuracy']
94 | record_scalars(record_dict)
95 | output_spec = tf.estimator.EstimatorSpec(mode=tf.estimator.ModeKeys.EVAL,
96 | loss=loss,
97 | predictions={'predict': tf.sigmoid(logits)},
98 | eval_metric_ops=metric_dict )
99 | else: # train
100 | # check whether restore from checkpoint
101 | tvars = tf.trainable_variables()
102 | initialized_variable_names = {}
103 |
104 | tf.logging.info("**** Trainable Variables ****")
105 | for var in tvars:
106 | init_string = ""
107 | if var.name in initialized_variable_names:
108 | init_string = ", *INIT_FROM_CKPT*"
109 | tf.logging.info(" name = %s, shape = %s%s", var.name, var.shape,
110 | init_string)
111 |
112 | train_op, metric_dict = get_train_op_and_metrics(loss, params)
113 | acc_metric = get_eval_metrics(logits, target_correct, target_ids, seq_steps)
114 | record_dict['accuracy'] = acc_metric['accuracy']
115 | record_dict['learning_rate'] = metric_dict['learning_rate']
116 | record_scalars(record_dict)
117 | output_spec = tf.estimator.EstimatorSpec(mode=tf.estimator.ModeKeys.TRAIN,
118 | loss=loss,
119 | train_op=train_op
120 | )
121 | return output_spec
122 | return model_fn
123 |
124 |
125 | def input_fn_builder(datadir, epoch):
126 | def input_fn():
127 | padded_shapes = {'inputs': [None],
128 | 'target_correct': [None],
129 | 'target_id': [None],
130 | 'ids': [None],
131 | 'correct': [None],
132 |
133 | 'seq_len': [],
134 | }
135 |
136 | dataset_train = get_dataset(datadir)
137 | dataset_train = dataset_train.repeat(epoch).shuffle(1000).padded_batch(FLAGS.batch_size,
138 | padded_shapes=padded_shapes)
139 |
140 | return dataset_train
141 |
142 | return input_fn
143 |
144 |
145 | def main():
146 | # if not FLAGS.do_train and not FLAGS.do_eval and not FLAGS.do_predict:
147 | # raise ValueError("At least one of `do_train`, `do_eval` or `do_predict' must be True.")
148 | params = BASE_PARAMS # param dict from model_params.py
149 | tf.gfile.MakeDirs(FLAGS.saved_model)
150 |
151 | run_config = tf.estimator.RunConfig(
152 | model_dir=FLAGS.saved_model,
153 | tf_random_seed=tf.set_random_seed([42]),
154 | # save_summary_step=100,
155 | save_checkpoints_steps=1000,
156 | # save_checkpoints_secs=600,
157 | session_config=None,
158 | keep_checkpoint_max=5,
159 | keep_checkpoint_every_n_hours=10000,
160 | log_step_count_steps=100,
161 | train_distribute=None
162 | )
163 | model_fn = model_fn_builder()
164 | estimator = tf.estimator.Estimator(
165 | model_fn=model_fn,
166 | model_dir=FLAGS.saved_model,
167 | config=run_config,
168 | params=params,
169 | warm_start_from=None
170 | )
171 | # train_input_fn = input_fn_builder(FLAGS.train_data, FLAGS.epoch)
172 | # train_spec = tf.estimator.TrainSpec(input_fn=train_input_fn,
173 | # max_steps=None)
174 | # eval_input_fn = input_fn_builder(FLAGS.valid_data, 1)
175 | # eval_spec = tf.estimator.EvalSpec(input_fn=eval_input_fn,
176 | # steps=None,
177 | # throttle_secs=60)
178 | # tf.estimator.train_and_evaluate(estimator,
179 | # train_spec=train_spec,
180 | # eval_spec=eval_spec)
181 | for epoch in range(FLAGS.epoch):
182 | if FLAGS.do_train:
183 | train_input_fn = input_fn_builder(FLAGS.train_data, 1)
184 |
185 | estimator.train(input_fn=train_input_fn)
186 | if FLAGS.do_eval:
187 | eval_input_fn = input_fn_builder(FLAGS.valid_data, 1)
188 | result = estimator.evaluate(input_fn=eval_input_fn)
189 |
190 | output_eval_file = os.path.join(FLAGS.output_dir, "eval_results.txt")
191 | with tf.gfile.GFile(output_eval_file, "w+") as writer:
192 | tf.logging.info("***** Eval results *****")
193 | for key in sorted(result.keys()):
194 | tf.logging.info(" %s = %s", key, str(result[key]))
195 | writer.write("%s = %s\n" % (key, str(result[key])))
196 |
197 |
198 | if __name__ == '__main__':
199 | tf.logging.set_verbosity(tf.logging.INFO)
200 | # flags.mark_flag_as_required("train_data")
201 | # flags.mark_flag_as_required("valid_data")
202 | # flags.mark_flag_as_required("saved_model")
203 |
204 | main()
205 |
--------------------------------------------------------------------------------
/code/DKT/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:utils.py
5 | @time:2019/8/125:04 PM
6 | """
7 | from __future__ import absolute_import
8 | from __future__ import division
9 | from __future__ import print_function
10 |
11 | import tensorflow as tf
12 | import collections
13 | import six
14 | import re
15 |
16 | def assert_rank(tensor, expected_rank, name=None):
17 | """Raises an exception if the tensor rank is not of the expected rank.
18 |
19 | Args:
20 | tensor: A tf.Tensor to check the rank of.
21 | expected_rank: Python integer or list of integers, expected rank.
22 | name: Optional name of the tensor for the error message.
23 |
24 | Raises:
25 | ValueError: If the expected shape doesn't match the actual shape.
26 | """
27 | if name is None:
28 | name = tensor.name
29 |
30 | expected_rank_dict = {}
31 | if isinstance(expected_rank, six.integer_types):
32 | expected_rank_dict[expected_rank] = True
33 | else:
34 | for x in expected_rank:
35 | expected_rank_dict[x] = True
36 |
37 | actual_rank = tensor.shape.ndims
38 | if actual_rank not in expected_rank_dict:
39 | scope_name = tf.get_variable_scope().name
40 | raise ValueError(
41 | "For the tensor `%s` in scope `%s`, the actual rank "
42 | "`%d` (shape = %s) is not equal to the expected rank `%s`" %
43 | (name, scope_name, actual_rank, str(tensor.shape), str(expected_rank)))
44 |
45 |
46 | def get_shape_list(tensor, expected_rank=None, name=None):
47 | """Returns a list of the shape of tensor, preferring static dimensions.
48 |
49 | Args:
50 | tensor: A tf.Tensor object to find the shape of.
51 | expected_rank: (optional) int. The expected rank of `tensor`. If this is
52 | specified and the `tensor` has a different rank, and exception will be
53 | thrown.
54 | name: Optional name of the tensor for the error message.
55 |
56 | Returns:
57 | A list of dimensions of the shape of tensor. All static dimensions will
58 | be returned as python integers, and dynamic dimensions will be returned
59 | as tf.Tensor scalars.
60 | """
61 | if name is None:
62 | name = tensor.name
63 |
64 | if expected_rank is not None:
65 | assert_rank(tensor, expected_rank, name)
66 |
67 | shape = tensor.shape.as_list()
68 |
69 | non_static_indexes = []
70 | for (index, dim) in enumerate(shape):
71 | if dim is None:
72 | non_static_indexes.append(index)
73 |
74 | if not non_static_indexes:
75 | return shape
76 |
77 | dyn_shape = tf.shape(tensor)
78 | for index in non_static_indexes:
79 | shape[index] = dyn_shape[index]
80 | return shape
81 |
82 |
83 | def get_learning_rate(learning_rate, hidden_size, learning_rate_warmup_steps):
84 | """calculate learning rate with linear warmup and rsqrt decay"""
85 | with tf.name_scope('learning_rate'):
86 | warmup_steps = tf.to_float(learning_rate_warmup_steps)
87 | step = tf.to_float(tf.train.get_or_create_global_step())
88 |
89 | learning_rate *= (hidden_size ** -0.5)
90 | # Apply linear warmup
91 | learning_rate *= tf.minimum(1.0, step/warmup_steps)
92 | # Apply rsqrt decay
93 | learning_rate *= tf.rsqrt(tf.maximum(step, warmup_steps))
94 |
95 | tf.identity(learning_rate, "learning_rate")
96 | return learning_rate
97 |
98 |
99 | def get_train_op_and_metrics(loss, params):
100 | """Generate training op and metrics to save in tensorboard"""
101 | with tf.variable_scope('get_train_op'):
102 | learning_rate = get_learning_rate(
103 | learning_rate=params['learning_rate'],
104 | hidden_size=params['hidden_size'],
105 | learning_rate_warmup_steps=params['learning_rate_warmup_steps']
106 | )
107 | # create optimizer , Use lazyAdamOptimizer from TF contrib,which is faster than the TF core Adam operation
108 | optimizer = tf.contrib.opt.LazyAdamOptimizer(
109 | learning_rate=learning_rate,
110 | beta1=params['optimizer_adam_beta1'],
111 | beta2=params['optimizer_adam_beta2'],
112 | epsilon=params['optimizer_adam_epsilon']
113 | )
114 | # calculate and apply graph gradient using LazyAdamOptimizer
115 | global_step = tf.train.get_global_step()
116 | tvars = tf.trainable_variables()
117 | gradients = optimizer.compute_gradients(loss, tvars, colocate_gradients_with_ops=True)
118 | minimize_op = optimizer.apply_gradients(gradients,
119 | global_step=global_step,
120 | name='train')
121 | update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
122 | train_op = tf.group(minimize_op, update_ops)
123 |
124 | train_metrics = {'learning_rate': learning_rate,
125 | 'global_step': global_step}
126 | return train_op, train_metrics
127 |
128 |
129 | def record_scalars(metric_dict):
130 | for key, value in metric_dict.items():
131 | print('records_scalars', key)
132 | if key == 'accuracy':
133 | tf.summary.scalar(name=key, tensor=value[1])
134 | else:
135 | tf.summary.scalar(name=key, tensor=value)
136 |
137 |
138 | def get_assignment_map_from_checkpoint(tvars, init_checkpoint):
139 | """Compute the union of the current variables and checkpoint variables."""
140 | assignment_map = {}
141 | initialized_variable_names = {}
142 |
143 | name_to_variable = collections.OrderedDict()
144 | for var in tvars:
145 | name = var.name
146 | m = re.match("^(.*):\\d+$", name)
147 | if m is not None:
148 | name = m.group(1)
149 | name_to_variable[name] = var
150 |
151 | init_vars = tf.train.list_variables(init_checkpoint)
152 |
153 | assignment_map = collections.OrderedDict()
154 | for x in init_vars:
155 | (name, var) = (x[0], x[1])
156 | if name not in name_to_variable:
157 | continue
158 | assignment_map[name] = name
159 | initialized_variable_names[name] = 1
160 | initialized_variable_names[name + ":0"] = 1
161 |
162 | return (assignment_map, initialized_variable_names)
163 |
164 |
165 | def parse_exmp(serial_exmp):
166 | feats = tf.parse_single_example(serial_exmp, features={'inputs': tf.VarLenFeature(tf.float32),
167 | 'target_correct': tf.VarLenFeature(tf.int64),
168 | 'target_id': tf.VarLenFeature(tf.float32),
169 |
170 | 'correct': tf.VarLenFeature(tf.int64),
171 | 'id': tf.VarLenFeature(tf.float32),
172 |
173 | 'seq_len': tf.FixedLenFeature([], tf.int64)})
174 | inputs = tf.sparse_tensor_to_dense(feats['inputs']) # 使用VarLenFeature读入的是一个sparse_tensor,用该函数进行转换
175 | target_correct = tf.sparse_tensor_to_dense(feats['target_correct'])
176 | target_id = tf.sparse_tensor_to_dense(feats['target_id'])
177 |
178 | correct = tf.sparse_tensor_to_dense(feats['correct'])
179 | id = tf.sparse_tensor_to_dense(feats['id'])
180 |
181 | inputs = tf.cast(inputs, tf.int32)
182 | target_correct = tf.cast(target_correct, tf.float32)
183 | target_id = tf.cast(target_id, tf.int32)
184 |
185 | correct = tf.cast(correct, tf.float32)
186 | id = tf.cast(id, tf.int32)
187 |
188 | seq_len = tf.cast(feats['seq_len'], tf.int32)
189 |
190 | return {'inputs': inputs,
191 | 'target_correct': target_correct,
192 | 'target_id': target_id,
193 | 'ids': id,
194 | 'correct': correct,
195 | 'seq_len': seq_len}
196 |
197 |
198 | def get_dataset(fname):
199 | dataset = tf.data.TFRecordDataset(fname)
200 | return dataset.map(parse_exmp) # use padded_batch method if padding needed
201 |
--------------------------------------------------------------------------------
/code/TCN-KT/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:__init__.py.py
5 | @time:2019/10/83:01 PM
6 | """
--------------------------------------------------------------------------------
/code/TCN-KT/metrics.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:metrics.py
5 | @time:2019/8/122:57 PM
6 | """
7 | import tensorflow as tf
8 | from model_params import *
9 | from model import DKT
10 |
11 |
12 | def dkt_loss(logits, target_correct, target_ids, seq_steps):
13 | """
14 | calculate cross-entropy loss for dkt
15 | Args:
16 | logits: logits(no sigmoid func) with shape [batch_size, length, vocab_size]
17 | target_correct: targets with shape [batch_size, length]
18 | target_ids:
19 | seq_steps:
20 |
21 | Returns:
22 | cross-entropy loss
23 |
24 | """
25 | with tf.name_scope('loss'):
26 | batch_size, length, vocab_size = tf.shape(logits)[0], tf.shape(logits)[1], tf.shape(logits)[2]
27 | flat_logits = tf.reshape(logits, [-1])
28 | flat_target_correct = tf.reshape(target_correct, [-1])
29 |
30 | flat_base_target_index = tf.range(batch_size * length) * vocab_size
31 | flat_bias_target_id = tf.reshape(target_ids, [-1])
32 | flat_target_id = flat_base_target_index + flat_bias_target_id
33 |
34 | flat_target_logits = tf.gather(flat_logits, flat_target_id)
35 | mask = tf.reshape(tf.sequence_mask(seq_steps, maxlen=length), [-1])
36 | # drop predict which user not react to
37 | flat_target_correct_mask = tf.boolean_mask(flat_target_correct, mask)
38 | flat_target_logits_mask = tf.boolean_mask(flat_target_logits, mask)
39 |
40 | loss = tf.reduce_mean(tf.nn.weighted_cross_entropy_with_logits(
41 | targets=flat_target_correct_mask,
42 | logits=flat_target_logits_mask,
43 | pos_weight=1
44 | ))
45 | return loss
46 |
47 |
48 | def padded_accuracy(logits, target, target_ids, seq_len):
49 | """
50 | Percentage of times that predictions matches albels on non-0s
51 | Args:
52 | logits: Tensor with shape [batch_size, length, vocab_size]
53 | target: Tesnor wtth shape [batch_size, length]
54 |
55 | Returns:
56 |
57 | """
58 | with tf.variable_scope('padded_accuracy', values=[logits, target, target_ids, seq_len]):
59 | batch_size, length, vocab_size = tf.shape(logits)[0], tf.shape(logits)[1], tf.shape(logits)[2]
60 | flat_logits = tf.reshape(logits, [-1])
61 | flat_target_correct = tf.reshape(target, [-1])
62 |
63 | flat_base_target_index = tf.range(batch_size * length) * vocab_size
64 | flat_bias_target_id = tf.reshape(target_ids, [-1])
65 | flat_target_id = flat_base_target_index + flat_bias_target_id
66 |
67 | flat_target_logits = tf.gather(flat_logits, flat_target_id)
68 | pred = tf.sigmoid(tf.reshape(flat_target_logits, [batch_size, length]))
69 | # self.binary_pred = tf.cast(tf.greater_equal(self.pred, 0.5), tf.int32)
70 | binary_pred = tf.cast(tf.greater(pred, 0.5), tf.float32)
71 |
72 | predict = tf.reshape(binary_pred, [-1])
73 | mask = tf.reshape(tf.sequence_mask(seq_len, maxlen=tf.shape(logits)[1]), [-1])
74 | flat_predict_mask = tf.boolean_mask(predict, mask)
75 | flat_target_mask = tf.boolean_mask(flat_target_correct, mask)
76 | return tf.equal(flat_target_mask, flat_predict_mask)
77 |
78 |
79 | def _convert_to_eval_metric(metric_fn):
80 | """
81 | warper a metric_fn that returns scores and weights as an eval metric fn
82 | The input metric_fn returns values for the current batch.
83 | The wrapper aggregates the return values collected over all of the batches evaluated
84 | Args:
85 | metric_fn: function that return socres for current batch's logits and targets
86 |
87 | Returns:
88 | function that aggregates the score and weights from metric_fn
89 |
90 | """
91 | def problem_metric_fn(*args):
92 | """
93 | return an aggregation of the metric_fn's returned values
94 | Args:
95 | *args:
96 |
97 | Returns:
98 |
99 | """
100 | score = metric_fn(*args)
101 | return tf.metrics.mean(score)
102 | return problem_metric_fn
103 |
104 |
105 | def get_eval_metrics(logits, labels, target_ids, seq_len):
106 | """
107 | return dictionary of model evaluation metrics
108 | Args:
109 | logits:
110 | labels:
111 | params:
112 |
113 | Returns:
114 |
115 | """
116 | metrics = {
117 | 'accuracy': _convert_to_eval_metric(padded_accuracy)(logits, labels, target_ids, seq_len),
118 | }
119 | return metrics
--------------------------------------------------------------------------------
/code/TCN-KT/model.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:model.py
5 | @time:2019/10/86:24 PM
6 | """
7 | from __future__ import absolute_import
8 | from __future__ import division
9 | from __future__ import print_function
10 |
11 | import tensorflow as tf
12 | from modules import TemporalConvNet
13 |
14 |
15 | class DKT(object):
16 | def __init__(self, params, train):
17 | """
18 | Initialize layers to build DKT model
19 | Args:
20 | params: hyperparameter object defining layer size, dropout value etc
21 | train: boolean indicating whether the model is in training mode
22 | """
23 | self.params = params
24 | self.train = train
25 | num_channels = [self.params['hidden_size'] * self.params['hidden_layer_num']]
26 | self.tcn = TemporalConvNet(num_channels,
27 | stride=1,
28 | kernel_size=self.params['kernel_size'],
29 | dropout=1-self.params['keep_prob'])
30 |
31 | def __call__(self, inputs):
32 |
33 | if self.params['use_one_hot']:
34 | self.embedding_output = tf.one_hot(inputs, depth=2*self.params['num_skills'] + 1)
35 | else: # Use Embedding
36 | embedding_table = tf.get_variable(
37 | name="word_embedding",
38 | shape=[2*self.params['num_skills']+1, self.params['hidden_size']],
39 | initializer=tf.truncated_normal_initializer(self.params['initializer_range']))
40 | self.embedding_output = tf.nn.embedding_lookup(embedding_table, inputs)
41 | self.embedding_output = tf.contrib.layers.layer_norm(self.embedding_output,
42 | begin_norm_axis=-1,
43 | begin_params_axis=-1, scope='embedding_ln')
44 | self.embedding_output = tf.nn.dropout(self.embedding_output, keep_prob=self.params['keep_prob'])
45 | tcn_output = self.tcn(self.embedding_output)
46 | logits = tf.layers.dense(tcn_output, self.params['num_skills'], activation=None, use_bias=True)
47 | return logits
--------------------------------------------------------------------------------
/code/TCN-KT/model_params.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:model_params.py
5 | @time:2019/10/83:31 PM
6 | """
7 | from collections import defaultdict
8 |
9 |
10 | BASE_PARAMS = defaultdict(
11 | # Input params
12 | num_skills=125,
13 |
14 | # Model params
15 | initializer_range=0.02, # embedding table initializer
16 | keep_prob=0.5,
17 | use_LN=True,
18 | hidden_layer_num=4,
19 | hidden_size=128,
20 | kernel_size = 3, # 卷积核大小
21 | use_one_hot=False,
22 |
23 | # Training params
24 | learning_rate=0.1,
25 | learning_rate_decay_rate=1.0,
26 | learning_rate_warmup_steps=16000,
27 |
28 | # Optimizer params
29 | optimizer_adam_beta1=0.9,
30 | optimizer_adam_beta2=0.997,
31 | optimizer_adam_epsilon=1e-09,
32 |
33 | )
--------------------------------------------------------------------------------
/code/TCN-KT/modules.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:modules.py
5 | @time:2019/10/86:25 PM
6 | """
7 | import tensorflow as tf
8 | from tensorflow.python.framework import tensor_shape
9 | from tensorflow.python.layers import base
10 | from tensorflow.python.layers import utils
11 | from tensorflow.python.layers import convolutional as convolutional_layers
12 | from tensorflow.python.ops import init_ops
13 | from tensorflow.python.ops import nn_ops
14 | from tensorflow.python.ops import nn_impl
15 |
16 |
17 | class TemporalConvNet(object):
18 | def __init__(self, num_channels, stride=1, kernel_size=2, dropout=0.2):
19 | self.kernel_size = kernel_size
20 | self.stride = stride
21 | self.num_levels = len(num_channels)
22 | self.num_channels = num_channels
23 | self.dropout = dropout
24 |
25 | def __call__(self, inputs):
26 | inputs_shape = inputs.get_shape().as_list()
27 | outputs = [inputs]
28 | for i in range(self.num_levels):
29 | dilation_size = 2 ** i
30 | in_channels = inputs_shape[-1] if i == 0 else self.num_channels[i - 1]
31 | out_channels = self.num_channels[i]
32 | output = self._TemporalBlock(outputs[-1], in_channels, out_channels, self.kernel_size,
33 | self.stride, dilation=dilation_size,
34 | padding=(self.kernel_size - 1) * dilation_size,
35 | dropout=self.dropout, level=i)
36 | outputs.append(output)
37 | # (batch, max_len, embedding_size)
38 | return outputs[-1]
39 |
40 | def _TemporalBlock(self, value, n_inputs, n_outputs, kernel_size, stride, dilation, padding, dropout=0.2, level=0):
41 | padded_value1 = tf.pad(value, [[0, 0], [padding, 0], [0, 0]])
42 | # 定义第一个空洞卷积层
43 | self.conv1 = wnconv1d(inputs=padded_value1,
44 | filters=n_outputs,
45 | kernel_size=kernel_size,
46 | strides=stride,
47 | padding='valid',
48 | dilation_rate=dilation,
49 | activation=None,
50 | weight_norm=True, # default is false.
51 | kernel_initializer=tf.random_normal_initializer(0, 0.01),
52 | bias_initializer=tf.zeros_initializer(),
53 | name='layer' + str(level) + '_conv1')
54 | # 添加激活函数与dropout正则化方法完成第一个卷积
55 | self.output1 = tf.nn.dropout(tf.nn.relu(self.conv1), keep_prob=1 - dropout)
56 |
57 | # 堆叠同样结构的第二个卷积层
58 | padded_value2 = tf.pad(self.output1, [[0, 0], [padding, 0], [0, 0]])
59 | self.conv2 = wnconv1d(inputs=padded_value2,
60 | filters=n_outputs,
61 | kernel_size=kernel_size,
62 | strides=stride,
63 | padding='valid',
64 | dilation_rate=dilation,
65 | activation=None,
66 | weight_norm=False, # default is False.
67 | kernel_initializer=tf.random_normal_initializer(0, 0.01),
68 | bias_initializer=tf.zeros_initializer(),
69 | name='layer' + str(level) + '_conv2')
70 | self.output2 = tf.nn.dropout(tf.nn.relu(self.conv2), keep_prob=1 - dropout)
71 | # 如果通道数不一样,那么需要对输入x做一个逐元素的一维卷积以使得它的纬度与前面两个卷积相等
72 | if n_inputs != n_outputs:
73 | res_x = tf.layers.conv1d(inputs=value,
74 | filters=n_outputs,
75 | kernel_size=1,
76 | activation=None,
77 | kernel_initializer=tf.random_normal_initializer(0, 0.01),
78 | bias_initializer=tf.zeros_initializer(),
79 | name='layer' + str(level) + '_conv')
80 | else:
81 | res_x = value
82 | return tf.nn.relu(res_x + self.output2)
83 |
84 |
85 | class _WNConv(convolutional_layers._Conv):
86 | def __init__(self, *args, **kwargs):
87 | self.weight_norm = kwargs.pop('weight_norm')
88 | super(_WNConv, self).__init__(*args, **kwargs)
89 |
90 | def build(self, input_shape):
91 | input_shape = tensor_shape.TensorShape(input_shape)
92 | if self.data_format == 'channels_first':
93 | channel_axis = 1
94 | else:
95 | channel_axis = -1
96 |
97 | if input_shape[channel_axis].value is None:
98 | raise ValueError('The channel dimension of the inputs '
99 | 'should be defined. Found `None`.')
100 | input_dim = input_shape[channel_axis].value
101 | # kernel_shape=(self.kernel_size, input_dim, self.filters)
102 | kernel_shape = self.kernel_size + (input_dim, self.filters)
103 |
104 | kernel = self.add_variable(name='kernel',
105 | shape=kernel_shape,
106 | initializer=self.kernel_initializer,
107 | regularizer=self.kernel_regularizer,
108 | constraint=self.kernel_constraint,
109 | trainable=True,
110 | dtype=self.dtype)
111 | # weight normalization
112 | if self.weight_norm:
113 | g = self.add_variable(name='wn/g',
114 | shape=(self.filters,),
115 | initializer=init_ops.ones_initializer(),
116 | dtype=kernel.dtype,
117 | trainable=True)
118 | self.kernel = tf.reshape(g, [1, 1, self.filters]) * nn_impl.l2_normalize(kernel, [0, 1])
119 | else:
120 | self.kernel = kernel
121 |
122 | if self.use_bias:
123 | self.bias = self.add_variable(name='bias',
124 | shape=(self.filters,),
125 | initializer=self.bias_initializer,
126 | regularizer=self.bias_regularizer,
127 | constraint=self.bias_constraint,
128 | trainable=True,
129 | dtype=self.dtype)
130 | else:
131 | self.bias = None
132 | self.input_spec = base.InputSpec(ndim=self.rank + 2,
133 | axes={channel_axis: input_dim})
134 | self._convolution_op = nn_ops.Convolution(
135 | input_shape,
136 | filter_shape=self.kernel.get_shape(),
137 | dilation_rate=self.dilation_rate,
138 | strides=self.strides,
139 | padding=self.padding.upper(),
140 | data_format=utils.convert_data_format(self.data_format,
141 | self.rank + 2))
142 | self.built = True
143 |
144 |
145 | class WNConv1D(_WNConv):
146 | def __init__(self, filters,
147 | kernel_size,
148 | strides=1,
149 | padding='valid',
150 | data_format='channels_last',
151 | dilation_rate=1,
152 | activation=None,
153 | weight_norm=False,
154 | use_bias=True,
155 | kernel_initializer=None,
156 | bias_initializer=init_ops.zeros_initializer(),
157 | kernel_regularizer=None,
158 | bias_regularizer=None,
159 | activity_regularizer=None,
160 | kernel_constraint=None,
161 | bias_constraint=None,
162 | trainable=True,
163 | name=None,
164 | **kwargs):
165 | super(WNConv1D, self).__init__(
166 | rank=1,
167 | filters=filters,
168 | kernel_size=kernel_size,
169 | strides=strides,
170 | padding=padding,
171 | data_format=data_format,
172 | dilation_rate=dilation_rate,
173 | activation=activation,
174 | weight_norm=weight_norm,
175 | use_bias=use_bias,
176 | kernel_initializer=kernel_initializer,
177 | bias_initializer=bias_initializer,
178 | kernel_regularizer=kernel_regularizer,
179 | bias_regularizer=bias_regularizer,
180 | activity_regularizer=activity_regularizer,
181 | kernel_constraint=kernel_constraint,
182 | bias_constraint=bias_constraint,
183 | trainable=trainable,
184 | name=name, **kwargs)
185 |
186 |
187 | def wnconv1d(inputs,
188 | filters,
189 | kernel_size,
190 | strides=1,
191 | padding='valid',
192 | data_format='channels_last',
193 | dilation_rate=1,
194 | activation=None,
195 | weight_norm=True,
196 | use_bias=True,
197 | kernel_initializer=None,
198 | bias_initializer=init_ops.zeros_initializer(),
199 | kernel_regularizer=None,
200 | bias_regularizer=None,
201 | activity_regularizer=None,
202 | kernel_constraint=None,
203 | bias_constraint=None,
204 | trainable=True,
205 | name=None,
206 | reuse=None):
207 | layer = WNConv1D(
208 | filters=filters,
209 | kernel_size=kernel_size,
210 | strides=strides,
211 | padding=padding,
212 | data_format=data_format,
213 | dilation_rate=dilation_rate,
214 | activation=activation,
215 | weight_norm=weight_norm,
216 | use_bias=use_bias,
217 | kernel_initializer=kernel_initializer,
218 | bias_initializer=bias_initializer,
219 | kernel_regularizer=kernel_regularizer,
220 | bias_regularizer=bias_regularizer,
221 | activity_regularizer=activity_regularizer,
222 | kernel_constraint=kernel_constraint,
223 | bias_constraint=bias_constraint,
224 | trainable=trainable,
225 | name=name,
226 | dtype=inputs.dtype.base_dtype,
227 | _reuse=reuse,
228 | _scope=name)
229 |
230 | return layer.apply(inputs)
231 |
--------------------------------------------------------------------------------
/code/TCN-KT/run_dkt.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:run_dkt.py
5 | @time:2019/8/1212:39 PM
6 | """
7 | from __future__ import absolute_import
8 | from __future__ import division
9 | from __future__ import print_function
10 |
11 | import tensorflow as tf
12 | import os
13 | import numpy as np
14 | from model_params import BASE_PARAMS
15 | from model import *
16 | from metrics import *
17 | from utils import *
18 |
19 |
20 |
21 | flags = tf.flags
22 | logging = tf.logging
23 |
24 | flags.DEFINE_string(name='train_data', default='../data/train.tfrecord',
25 | help='Path of training data ')
26 | flags.DEFINE_string(name='valid_data', default='../data/eval.tfrecord',
27 | help='Path of valid data')
28 | flags.DEFINE_string(name='predict_data', default='',
29 | help='Path of predict data')
30 | flags.DEFINE_string(name='saved_model', default='./saved_model', help='Path to save model')
31 | flags.DEFINE_string(name='output_dir', default='./output_dir', help='Path to save model')
32 | flags.DEFINE_integer(name='epoch', default=100, help='Num of epoch')
33 | flags.DEFINE_integer(name='batch_size', default=1, help='Num of batch')
34 | flags.DEFINE_bool(name="do_train", default=True, help="Whether to run training.")
35 | flags.DEFINE_bool(name="do_eval", default=True, help="Whether to run eval on the dev set.")
36 | flags.DEFINE_bool(
37 | "do_predict", False,
38 | "Whether to run the model in inference mode on the test set.")
39 | flags.DEFINE_bool(name='pb', default=True, help='')
40 | FLAGS = flags.FLAGS
41 |
42 |
43 | def model_fn_builder():
44 | def model_fn(features, labels, mode, params):
45 | """
46 | define how to train, evaluate and predict from the transfomer model.
47 | Args:
48 |
49 | mode:
50 | params:
51 |
52 | Returns:
53 |
54 | """
55 | inputs = features['inputs']
56 | seq_steps = features['seq_len']
57 |
58 |
59 | is_training = (mode == tf.estimator.ModeKeys.TRAIN)
60 |
61 | try:
62 | batch_size, length = get_shape_list(inputs, expected_rank=2)
63 | except ValueError:
64 | batch_size = 1
65 | length = get_shape_list(inputs, expected_rank=1)[0]
66 | inputs = tf.reshape(inputs, [batch_size, length])
67 |
68 | with tf.variable_scope('model'):
69 | # Build model
70 | model = DKT(params, is_training)
71 | logits = model(inputs) # [batch, length, vocab_size]
72 |
73 | # when in prediction mode, the label/target is Bone, the model output is the prediction
74 | if mode == tf.estimator.ModeKeys.PREDICT:
75 | export_outputs = {'predict_output': tf.estimator.export.PredictOutput({"predict": tf.sigmoid(logits)})}
76 | output_spec = tf.estimator.EstimatorSpec(mode=mode,
77 | predictions={'predict': tf.sigmoid(logits)},
78 | export_outputs=export_outputs
79 | )
80 | else:
81 | # Calculate model loss
82 | target_ids = features['target_id']
83 | target_correct = features['target_correct']
84 |
85 | loss = dkt_loss(logits, target_correct, target_ids, seq_steps)
86 | record_dict = {}
87 | record_dict['minibatch_loss'] = loss
88 | # Save loss as named tensor will be logged with the logging hook
89 | tf.identity(loss, 'cross_entropy')
90 |
91 | if mode == tf.estimator.ModeKeys.EVAL:
92 | metric_dict = get_eval_metrics(logits, target_correct, target_ids, seq_steps)
93 | record_dict['accuracy'] = metric_dict['accuracy']
94 | record_scalars(record_dict)
95 | output_spec = tf.estimator.EstimatorSpec(mode=tf.estimator.ModeKeys.EVAL,
96 | loss=loss,
97 | predictions={'predict': tf.sigmoid(logits)},
98 | eval_metric_ops=metric_dict )
99 | else: # train
100 | # check whether restore from checkpoint
101 | tvars = tf.trainable_variables()
102 | initialized_variable_names = {}
103 |
104 | tf.logging.info("**** Trainable Variables ****")
105 | for var in tvars:
106 | init_string = ""
107 | if var.name in initialized_variable_names:
108 | init_string = ", *INIT_FROM_CKPT*"
109 | tf.logging.info(" name = %s, shape = %s%s", var.name, var.shape,
110 | init_string)
111 |
112 | train_op, metric_dict = get_train_op_and_metrics(loss, params)
113 | acc_metric = get_eval_metrics(logits, target_correct, target_ids, seq_steps)
114 | record_dict['accuracy'] = acc_metric['accuracy']
115 | record_dict['learning_rate'] = metric_dict['learning_rate']
116 | record_scalars(record_dict)
117 | output_spec = tf.estimator.EstimatorSpec(mode=tf.estimator.ModeKeys.TRAIN,
118 | loss=loss,
119 | train_op=train_op
120 | )
121 | return output_spec
122 | return model_fn
123 |
124 |
125 | def input_fn_builder(datadir, epoch):
126 | def input_fn():
127 | padded_shapes = {'inputs': [None],
128 | 'target_correct': [None],
129 | 'target_id': [None],
130 | 'ids': [None],
131 | 'correct': [None],
132 |
133 | 'seq_len': [],
134 | }
135 |
136 | dataset_train = get_dataset(datadir)
137 | dataset_train = dataset_train.repeat(epoch).shuffle(1000).padded_batch(FLAGS.batch_size,
138 | padded_shapes=padded_shapes)
139 |
140 | return dataset_train
141 |
142 | return input_fn
143 |
144 |
145 | def main():
146 | # if not FLAGS.do_train and not FLAGS.do_eval and not FLAGS.do_predict:
147 | # raise ValueError("At least one of `do_train`, `do_eval` or `do_predict' must be True.")
148 | params = BASE_PARAMS # param dict from model_params.py
149 | tf.gfile.MakeDirs(FLAGS.saved_model)
150 |
151 | run_config = tf.estimator.RunConfig(
152 | model_dir=FLAGS.saved_model,
153 | tf_random_seed=tf.set_random_seed([42]),
154 | # save_summary_step=100,
155 | save_checkpoints_steps=1000,
156 | # save_checkpoints_secs=600,
157 | session_config=None,
158 | keep_checkpoint_max=5,
159 | keep_checkpoint_every_n_hours=10000,
160 | log_step_count_steps=100,
161 | train_distribute=None
162 | )
163 | model_fn = model_fn_builder()
164 | estimator = tf.estimator.Estimator(
165 | model_fn=model_fn,
166 | model_dir=FLAGS.saved_model,
167 | config=run_config,
168 | params=params,
169 | warm_start_from=None
170 | )
171 | # train_input_fn = input_fn_builder(FLAGS.train_data, FLAGS.epoch)
172 | # train_spec = tf.estimator.TrainSpec(input_fn=train_input_fn,
173 | # max_steps=None)
174 | # eval_input_fn = input_fn_builder(FLAGS.valid_data, 1)
175 | # eval_spec = tf.estimator.EvalSpec(input_fn=eval_input_fn,
176 | # steps=None,
177 | # throttle_secs=60)
178 | # tf.estimator.train_and_evaluate(estimator,
179 | # train_spec=train_spec,
180 | # eval_spec=eval_spec)
181 | for epoch in range(FLAGS.epoch):
182 | if FLAGS.do_train:
183 | train_input_fn = input_fn_builder(FLAGS.train_data, 1)
184 |
185 | estimator.train(input_fn=train_input_fn)
186 | if FLAGS.do_eval:
187 | eval_input_fn = input_fn_builder(FLAGS.valid_data, 1)
188 | result = estimator.evaluate(input_fn=eval_input_fn)
189 |
190 | output_eval_file = os.path.join(FLAGS.output_dir, "eval_results.txt")
191 | with tf.gfile.GFile(output_eval_file, "w+") as writer:
192 | tf.logging.info("***** Eval results *****")
193 | for key in sorted(result.keys()):
194 | tf.logging.info(" %s = %s", key, str(result[key]))
195 | writer.write("%s = %s\n" % (key, str(result[key])))
196 |
197 |
198 | if __name__ == '__main__':
199 | tf.logging.set_verbosity(tf.logging.INFO)
200 | # flags.mark_flag_as_required("train_data")
201 | # flags.mark_flag_as_required("valid_data")
202 | # flags.mark_flag_as_required("saved_model")
203 |
204 | main()
205 |
--------------------------------------------------------------------------------
/code/TCN-KT/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:utils.py
5 | @time:2019/8/125:04 PM
6 | """
7 | from __future__ import absolute_import
8 | from __future__ import division
9 | from __future__ import print_function
10 |
11 | import tensorflow as tf
12 | import collections
13 | import six
14 | import re
15 |
16 | def assert_rank(tensor, expected_rank, name=None):
17 | """Raises an exception if the tensor rank is not of the expected rank.
18 |
19 | Args:
20 | tensor: A tf.Tensor to check the rank of.
21 | expected_rank: Python integer or list of integers, expected rank.
22 | name: Optional name of the tensor for the error message.
23 |
24 | Raises:
25 | ValueError: If the expected shape doesn't match the actual shape.
26 | """
27 | if name is None:
28 | name = tensor.name
29 |
30 | expected_rank_dict = {}
31 | if isinstance(expected_rank, six.integer_types):
32 | expected_rank_dict[expected_rank] = True
33 | else:
34 | for x in expected_rank:
35 | expected_rank_dict[x] = True
36 |
37 | actual_rank = tensor.shape.ndims
38 | if actual_rank not in expected_rank_dict:
39 | scope_name = tf.get_variable_scope().name
40 | raise ValueError(
41 | "For the tensor `%s` in scope `%s`, the actual rank "
42 | "`%d` (shape = %s) is not equal to the expected rank `%s`" %
43 | (name, scope_name, actual_rank, str(tensor.shape), str(expected_rank)))
44 |
45 |
46 | def get_shape_list(tensor, expected_rank=None, name=None):
47 | """Returns a list of the shape of tensor, preferring static dimensions.
48 |
49 | Args:
50 | tensor: A tf.Tensor object to find the shape of.
51 | expected_rank: (optional) int. The expected rank of `tensor`. If this is
52 | specified and the `tensor` has a different rank, and exception will be
53 | thrown.
54 | name: Optional name of the tensor for the error message.
55 |
56 | Returns:
57 | A list of dimensions of the shape of tensor. All static dimensions will
58 | be returned as python integers, and dynamic dimensions will be returned
59 | as tf.Tensor scalars.
60 | """
61 | if name is None:
62 | name = tensor.name
63 |
64 | if expected_rank is not None:
65 | assert_rank(tensor, expected_rank, name)
66 |
67 | shape = tensor.shape.as_list()
68 |
69 | non_static_indexes = []
70 | for (index, dim) in enumerate(shape):
71 | if dim is None:
72 | non_static_indexes.append(index)
73 |
74 | if not non_static_indexes:
75 | return shape
76 |
77 | dyn_shape = tf.shape(tensor)
78 | for index in non_static_indexes:
79 | shape[index] = dyn_shape[index]
80 | return shape
81 |
82 |
83 | def get_learning_rate(learning_rate, hidden_size, learning_rate_warmup_steps):
84 | """calculate learning rate with linear warmup and rsqrt decay"""
85 | with tf.name_scope('learning_rate'):
86 | warmup_steps = tf.to_float(learning_rate_warmup_steps)
87 | step = tf.to_float(tf.train.get_or_create_global_step())
88 |
89 | learning_rate *= (hidden_size ** -0.5)
90 | # Apply linear warmup
91 | learning_rate *= tf.minimum(1.0, step/warmup_steps)
92 | # Apply rsqrt decay
93 | learning_rate *= tf.rsqrt(tf.maximum(step, warmup_steps))
94 |
95 | tf.identity(learning_rate, "learning_rate")
96 | return learning_rate
97 |
98 |
99 | def get_train_op_and_metrics(loss, params):
100 | """Generate training op and metrics to save in tensorboard"""
101 | with tf.variable_scope('get_train_op'):
102 | learning_rate = get_learning_rate(
103 | learning_rate=params['learning_rate'],
104 | hidden_size=params['hidden_size'],
105 | learning_rate_warmup_steps=params['learning_rate_warmup_steps']
106 | )
107 | # create optimizer , Use lazyAdamOptimizer from TF contrib,which is faster than the TF core Adam operation
108 | optimizer = tf.contrib.opt.LazyAdamOptimizer(
109 | learning_rate=learning_rate,
110 | beta1=params['optimizer_adam_beta1'],
111 | beta2=params['optimizer_adam_beta2'],
112 | epsilon=params['optimizer_adam_epsilon']
113 | )
114 | # calculate and apply graph gradient using LazyAdamOptimizer
115 | global_step = tf.train.get_global_step()
116 | tvars = tf.trainable_variables()
117 | gradients = optimizer.compute_gradients(loss, tvars, colocate_gradients_with_ops=True)
118 | minimize_op = optimizer.apply_gradients(gradients,
119 | global_step=global_step,
120 | name='train')
121 | update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
122 | train_op = tf.group(minimize_op, update_ops)
123 |
124 | train_metrics = {'learning_rate': learning_rate,
125 | 'global_step': global_step}
126 | return train_op, train_metrics
127 |
128 |
129 | def record_scalars(metric_dict):
130 | for key, value in metric_dict.items():
131 | print('records_scalars', key)
132 | if key == 'accuracy':
133 | tf.summary.scalar(name=key, tensor=value[1])
134 | else:
135 | tf.summary.scalar(name=key, tensor=value)
136 |
137 |
138 | def get_assignment_map_from_checkpoint(tvars, init_checkpoint):
139 | """Compute the union of the current variables and checkpoint variables."""
140 | assignment_map = {}
141 | initialized_variable_names = {}
142 |
143 | name_to_variable = collections.OrderedDict()
144 | for var in tvars:
145 | name = var.name
146 | m = re.match("^(.*):\\d+$", name)
147 | if m is not None:
148 | name = m.group(1)
149 | name_to_variable[name] = var
150 |
151 | init_vars = tf.train.list_variables(init_checkpoint)
152 |
153 | assignment_map = collections.OrderedDict()
154 | for x in init_vars:
155 | (name, var) = (x[0], x[1])
156 | if name not in name_to_variable:
157 | continue
158 | assignment_map[name] = name
159 | initialized_variable_names[name] = 1
160 | initialized_variable_names[name + ":0"] = 1
161 |
162 | return (assignment_map, initialized_variable_names)
163 |
164 |
165 | def parse_exmp(serial_exmp):
166 | feats = tf.parse_single_example(serial_exmp, features={'inputs': tf.VarLenFeature(tf.float32),
167 | 'target_correct': tf.VarLenFeature(tf.int64),
168 | 'target_id': tf.VarLenFeature(tf.float32),
169 |
170 | 'correct': tf.VarLenFeature(tf.int64),
171 | 'id': tf.VarLenFeature(tf.float32),
172 |
173 | 'seq_len': tf.FixedLenFeature([], tf.int64)})
174 | inputs = tf.sparse_tensor_to_dense(feats['inputs']) # 使用VarLenFeature读入的是一个sparse_tensor,用该函数进行转换
175 | target_correct = tf.sparse_tensor_to_dense(feats['target_correct'])
176 | target_id = tf.sparse_tensor_to_dense(feats['target_id'])
177 |
178 | correct = tf.sparse_tensor_to_dense(feats['correct'])
179 | id = tf.sparse_tensor_to_dense(feats['id'])
180 |
181 | inputs = tf.cast(inputs, tf.int32)
182 | target_correct = tf.cast(target_correct, tf.float32)
183 | target_id = tf.cast(target_id, tf.int32)
184 |
185 | correct = tf.cast(correct, tf.float32)
186 | id = tf.cast(id, tf.int32)
187 |
188 | seq_len = tf.cast(feats['seq_len'], tf.int32)
189 |
190 | return {'inputs': inputs,
191 | 'target_correct': target_correct,
192 | 'target_id': target_id,
193 | 'ids': id,
194 | 'correct': correct,
195 | 'seq_len': seq_len}
196 |
197 |
198 | def get_dataset(fname):
199 | dataset = tf.data.TFRecordDataset(fname)
200 | return dataset.map(parse_exmp) # use padded_batch method if padding needed
201 |
--------------------------------------------------------------------------------
/code/Transformer-KT/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:__init__.py.py
5 | @time:2019/8/911:17 AM
6 | """
--------------------------------------------------------------------------------
/code/Transformer-KT/metrics.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:metrics.py
5 | @time:2019/8/122:57 PM
6 | """
7 | import tensorflow as tf
8 | from model_params import *
9 | from model import DKT
10 |
11 |
12 | def dkt_loss(logits, target_correct, target_ids, seq_steps):
13 | """
14 | calculate cross-entropy loss for dkt
15 | Args:
16 | logits: logits(no sigmoid func) with shape [batch_size, length, vocab_size]
17 | target_correct: targets with shape [batch_size, length]
18 | target_ids:
19 | seq_steps:
20 |
21 | Returns:
22 | cross-entropy loss
23 |
24 | """
25 | with tf.name_scope('loss'):
26 | batch_size, length, vocab_size = tf.shape(logits)[0], tf.shape(logits)[1], tf.shape(logits)[2]
27 | flat_logits = tf.reshape(logits, [-1])
28 | flat_target_correct = tf.reshape(target_correct, [-1])
29 |
30 | flat_base_target_index = tf.range(batch_size * length) * vocab_size
31 | flat_bias_target_id = tf.reshape(target_ids, [-1])
32 | flat_target_id = flat_base_target_index + flat_bias_target_id
33 |
34 | flat_target_logits = tf.gather(flat_logits, flat_target_id)
35 | mask = tf.reshape(tf.sequence_mask(seq_steps, maxlen=length), [-1])
36 | # drop predict which user not react to
37 | flat_target_correct_mask = tf.boolean_mask(flat_target_correct, mask)
38 | flat_target_logits_mask = tf.boolean_mask(flat_target_logits, mask)
39 |
40 | loss = tf.reduce_mean(tf.nn.weighted_cross_entropy_with_logits(
41 | targets=flat_target_correct_mask,
42 | logits=flat_target_logits_mask,
43 | pos_weight=1
44 | ))
45 | return loss
46 |
47 |
48 | def padded_accuracy(logits, target, target_ids, seq_len):
49 | """
50 | Percentage of times that predictions matches albels on non-0s
51 | Args:
52 | logits: Tensor with shape [batch_size, length, vocab_size]
53 | target: Tesnor wtth shape [batch_size, length]
54 |
55 | Returns:
56 |
57 | """
58 | with tf.variable_scope('padded_accuracy', values=[logits, target, target_ids, seq_len]):
59 | batch_size, length, vocab_size = tf.shape(logits)[0], tf.shape(logits)[1], tf.shape(logits)[2]
60 | flat_logits = tf.reshape(logits, [-1])
61 | flat_target_correct = tf.reshape(target, [-1])
62 |
63 | flat_base_target_index = tf.range(batch_size * length) * vocab_size
64 | flat_bias_target_id = tf.reshape(target_ids, [-1])
65 | flat_target_id = flat_base_target_index + flat_bias_target_id
66 |
67 | flat_target_logits = tf.gather(flat_logits, flat_target_id)
68 | pred = tf.sigmoid(tf.reshape(flat_target_logits, [batch_size, length]))
69 | # self.binary_pred = tf.cast(tf.greater_equal(self.pred, 0.5), tf.int32)
70 | binary_pred = tf.cast(tf.greater(pred, 0.5), tf.float32)
71 |
72 | predict = tf.reshape(binary_pred, [-1])
73 | mask = tf.reshape(tf.sequence_mask(seq_len, maxlen=tf.shape(logits)[1]), [-1])
74 | flat_predict_mask = tf.boolean_mask(predict, mask)
75 | flat_target_mask = tf.boolean_mask(flat_target_correct, mask)
76 | return tf.equal(flat_target_mask, flat_predict_mask)
77 |
78 |
79 | def _convert_to_eval_metric(metric_fn):
80 | """
81 | warper a metric_fn that returns scores and weights as an eval metric fn
82 | The input metric_fn returns values for the current batch.
83 | The wrapper aggregates the return values collected over all of the batches evaluated
84 | Args:
85 | metric_fn: function that return socres for current batch's logits and targets
86 |
87 | Returns:
88 | function that aggregates the score and weights from metric_fn
89 |
90 | """
91 | def problem_metric_fn(*args):
92 | """
93 | return an aggregation of the metric_fn's returned values
94 | Args:
95 | *args:
96 |
97 | Returns:
98 |
99 | """
100 | score = metric_fn(*args)
101 | return tf.metrics.mean(score)
102 | return problem_metric_fn
103 |
104 |
105 | def get_eval_metrics(logits, labels, target_ids, seq_len):
106 | """
107 | return dictionary of model evaluation metrics
108 | Args:
109 | logits:
110 | labels:
111 | params:
112 |
113 | Returns:
114 |
115 | """
116 | metrics = {
117 | 'accuracy': _convert_to_eval_metric(padded_accuracy)(logits, labels, target_ids, seq_len),
118 | }
119 | return metrics
--------------------------------------------------------------------------------
/code/Transformer-KT/model.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:model.py
5 | @time:2019/8/95:18 PM
6 | """
7 | from __future__ import absolute_import
8 | from __future__ import division
9 | from __future__ import print_function
10 |
11 | import tensorflow as tf
12 | from modules import *
13 | import six
14 |
15 |
16 | def assert_rank(tensor, expected_rank, name=None):
17 | """Raises an exception if the tensor rank is not of the expected rank.
18 |
19 | Args:
20 | tensor: A tf.Tensor to check the rank of.
21 | expected_rank: Python integer or list of integers, expected rank.
22 | name: Optional name of the tensor for the error message.
23 |
24 | Raises:
25 | ValueError: If the expected shape doesn't match the actual shape.
26 | """
27 | if name is None:
28 | name = tensor.name
29 |
30 | expected_rank_dict = {}
31 | if isinstance(expected_rank, six.integer_types):
32 | expected_rank_dict[expected_rank] = True
33 | else:
34 | for x in expected_rank:
35 | expected_rank_dict[x] = True
36 |
37 | actual_rank = tensor.shape.ndims
38 | if actual_rank not in expected_rank_dict:
39 | scope_name = tf.get_variable_scope().name
40 | raise ValueError(
41 | "For the tensor `%s` in scope `%s`, the actual rank "
42 | "`%d` (shape = %s) is not equal to the expected rank `%s`" %
43 | (name, scope_name, actual_rank, str(tensor.shape), str(expected_rank)))
44 |
45 |
46 | def get_shape_list(tensor, expected_rank=None, name=None):
47 | """Returns a list of the shape of tensor, preferring static dimensions.
48 |
49 | Args:
50 | tensor: A tf.Tensor object to find the shape of.
51 | expected_rank: (optional) int. The expected rank of `tensor`. If this is
52 | specified and the `tensor` has a different rank, and exception will be
53 | thrown.
54 | name: Optional name of the tensor for the error message.
55 |
56 | Returns:
57 | A list of dimensions of the shape of tensor. All static dimensions will
58 | be returned as python integers, and dynamic dimensions will be returned
59 | as tf.Tensor scalars.
60 | """
61 | if name is None:
62 | name = tensor.name
63 |
64 | if expected_rank is not None:
65 | assert_rank(tensor, expected_rank, name)
66 |
67 | shape = tensor.shape.as_list()
68 |
69 | non_static_indexes = []
70 | for (index, dim) in enumerate(shape):
71 | if dim is None:
72 | non_static_indexes.append(index)
73 |
74 | if not non_static_indexes:
75 | return shape
76 |
77 | dyn_shape = tf.shape(tensor)
78 | for index in non_static_indexes:
79 | shape[index] = dyn_shape[index]
80 | return shape
81 |
82 |
83 | class DKT(object):
84 | def __init__(self, params, train):
85 | """
86 | Initialize layers to build DKT model
87 | Args:
88 | params: hyperparameter object defining layer size, dropout value etc
89 | train: boolean indicating whether the model is in training mode
90 | """
91 | self.train = train
92 | self.params = params
93 |
94 |
95 |
96 | self.inputs_embedding = EmbeddingShareWeights(vocab_size= 2*params['vocab_size'],
97 | hidden_size=params['hidden_size'])
98 | self.target_idx_embedding = EmbeddingShareWeights(vocab_size=params['vocab_size'],
99 | hidden_size=params['hidden_size'])
100 | self.encoder = EncoderStack(self.params, self.train)
101 |
102 | def __call__(self, inputs, target_ids):
103 | """
104 | Calculate logits or inferred target sequence
105 | Args:
106 | self:
107 | inputs: int tensor with shape [batch, input_length] question & reaction encoding
108 | target_ids: int tensor with shape [batch, input_length] question encoding
109 |
110 | input_padding:
111 |
112 | Returns:
113 |
114 | """
115 | input_shape = get_shape_list(inputs, expected_rank=2)
116 | batch_size = input_shape[0]
117 | length = input_shape[1]
118 |
119 | target_ids = tf.reshape(target_ids, [batch_size, length])
120 | initializer = tf.variance_scaling_initializer(
121 | self.params['initializer_gain'],
122 | mode="fan_avg",
123 | distribution="uniform")
124 | with tf.variable_scope('DKT', initializer=initializer):
125 | inputs_embeddings = self.inputs_embedding(inputs) # shape = [batch, length, hidden_size-feature_size]
126 |
127 | target_ids_embeddings = self.target_idx_embedding(target_ids) # shape=[batch, length, hidden_size]
128 |
129 | with tf.name_scope("add_pos_encoding"):
130 | length = tf.shape(inputs_embeddings)[1]
131 | pos_encoding = position_encoding(length, self.params['hidden_size'])
132 | encoder_key = inputs_embeddings + pos_encoding
133 | encoder_query = target_ids_embeddings + pos_encoding
134 |
135 | if self.train:
136 | encoder_key = tf.nn.dropout(encoder_key, keep_prob=1-self.params['layer_postprocess_dropout'])
137 | encoder_query = tf.nn.dropout(encoder_query, keep_prob=1-self.params['layer_postprocess_dropout'])
138 | attention_bias = get_padding_bias(encoder_key)
139 | inputs_padding = get_padding(encoder_key)
140 | # shape=[batch, length, hidden_size]
141 | transformer_output = self.encoder(encoder_key, encoder_query=encoder_query, encoder_key=encoder_key,
142 | attention_bias=attention_bias, inputs_padding=inputs_padding)
143 |
144 | with tf.name_scope("Outout_layer"):
145 | # shape=[batch, length, vocab_size]
146 | logits = tf.layers.dense(transformer_output, units=self.params['vocab_size'], activation=None) # linear layer
147 |
148 | return logits
149 |
150 |
151 | class PrepostProcessingWrapper(object):
152 | """Wrapper class that applies layer pre-processing and post-processing"""
153 |
154 | def __init__(self, layer, params, train):
155 | self.layer = layer
156 | self.postprocess_dropout = params['layer_postprocess_dropout']
157 | self.train = train
158 |
159 | self.layer_norm = LayerNormalization(params['hidden_size'])
160 |
161 | def __call__(self, x, *args, **kwargs):
162 | y = self.layer_norm(x)
163 |
164 | y = self.layer(y, *args, **kwargs)
165 | if self.train:
166 | y = tf.nn.dropout(y, keep_prob=1 - self.postprocess_dropout)
167 | return x + y
168 |
169 |
170 | class EncoderStack(tf.layers.Layer):
171 | """Transfomer encoder stack"""
172 | def __init__(self, params, train):
173 | super(EncoderStack, self).__init__()
174 | self.layers = []
175 |
176 | for i in range(params['num_hidden_layers']):
177 | # create sublayers for each layer
178 | self_attention_layer = Attention(hidden_size=params['hidden_size'],
179 | num_head=params['num_heads'],
180 | attention_dropout=params['attention_dropout'],
181 | train=train)
182 | feed_forward_netword = FeedForwardNetwork(hidden_size=params['hidden_size'],
183 | filter_size=params['filter_size'],
184 | relu_dropout=params['relu_dropout'],
185 | train=train,
186 | allow_pad=params['allow_ffn_pad'])
187 | self.layers.append([
188 | PrepostProcessingWrapper(self_attention_layer, params, train),
189 | PrepostProcessingWrapper(feed_forward_netword, params, train)
190 | ])
191 |
192 | self.output_normalization = LayerNormalization(params['hidden_size'])
193 |
194 | def call(self, inputs, encoder_query, encoder_key, attention_bias, inputs_padding):
195 | """
196 | Return the output of the encoder of layer stacks
197 | Args:
198 | encoder_query: tensor with shape [batch_size, input_length, hidden_size] query
199 | encoder_key: tensor with shape [batch_size, input_length, hidden_size] key & value
200 | attention_bias: bias for encoder self-attention layer [batch, 1, 1, input_length]
201 | input_padding: Padding
202 |
203 | Returns:
204 | output of encoder layer stack.
205 | float 32 tensor with shape [batch_size, input_length, hidden_size]
206 | """
207 | for n, layer in enumerate(self.layers):
208 | self_attention_layer = layer[0]
209 | feed_forward_network = layer[1]
210 |
211 | with tf.variable_scope("layer_%d" %n):
212 | with tf.variable_scope("self_attention"):
213 | if n == 0:
214 | encoder_inputs = self_attention_layer(x=encoder_query, y=encoder_key, bias=attention_bias)
215 | else:
216 | encoder_inputs = self_attention_layer(x=encoder_inputs, y=encoder_inputs, bias=attention_bias)
217 | with tf.variable_scope("ffn"):
218 | encoder_inputs = feed_forward_network(encoder_inputs, padding=None)
219 | return self.output_normalization(encoder_inputs)
220 |
--------------------------------------------------------------------------------
/code/Transformer-KT/model_params.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:model_params.py
5 | @time:2019/8/1212:42 PM
6 | """
7 | from collections import defaultdict
8 |
9 |
10 | BASE_PARAMS = defaultdict(
11 | # Input params
12 | default_batch_size=256, # Maximum number of tokens per batch of examples.
13 | max_length=512, # Maximum number of tokens per example.
14 |
15 |
16 | # Model Params
17 | initializer_gain=1.0, # Used in trainable variable initialization
18 | vocab_size=124, # unique tokes defined in the vocabulary (num_skills)
19 | hidden_size=256, # hidden size
20 | num_hidden_layers=2, # number of layers in the encoder
21 | num_heads=8, # number of heads to use in the multi-head attention
22 |
23 | filter_size=512, # Inner layer dimension in the feedward netword
24 | allow_ffn_pad=True,
25 |
26 | # Dropout values(Only used in training)
27 | layer_postprocess_dropout=0.1,
28 | attention_dropout=0.1,
29 | relu_dropout=0.1,
30 |
31 |
32 | # Training params
33 | learning_rate=0.1,
34 | learning_rate_decay_rate=1.0,
35 | learning_rate_warmup_steps=16000,
36 |
37 | # Optimizer params
38 | optimizer_adam_beta1=0.9,
39 | optimizer_adam_beta2=0.997,
40 | optimizer_adam_epsilon=1e-09,
41 |
42 | )
--------------------------------------------------------------------------------
/code/Transformer-KT/modules.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:modules.py
5 | @time:2019/8/911:18 AM
6 | """
7 | from __future__ import absolute_import
8 | from __future__ import division
9 | from __future__ import print_function
10 |
11 | import tensorflow as tf
12 | import math
13 | import six
14 |
15 |
16 | class EmbeddingShareWeights(tf.layers.Layer):
17 | """
18 | Calculates input embeddings
19 | """
20 | def __init__(self, vocab_size, hidden_size):
21 | """
22 | Specify characteristic parameters of embedding layer
23 | Args:
24 | vocab_size: Number of tokens in the embedding
25 | hidden_size: Dimensionality of the embedding.
26 | """
27 | super(EmbeddingShareWeights, self).__init__()
28 | self.vocab_size = vocab_size
29 | self.hidden_size = hidden_size
30 |
31 | def build(self, _):
32 | with tf.variable_scope("embedding_n_softmax", reuse=tf.AUTO_REUSE):
33 | """
34 | Create the initialize weights
35 | """
36 | self.shared_weight = tf.get_variable(
37 | name="weights", shape=[self.vocab_size, self.hidden_size],
38 | initializer=tf.random_normal_initializer(mean=0.0, stddev=self.hidden_size ** -0.5))
39 | self.built = True
40 |
41 | def call(self, x):
42 | """
43 | Get token embeddings of x
44 | Args:
45 | x: An int64 tensor with shape [batch, length]
46 | Returns:
47 | embeddings: float32. Tensor with shape [batch, length, embedding_szie]
48 | padding: float32. Tensor with shape [batch, length] indicating the locations of the padding tokens in x.
49 | """
50 | with tf.name_scope("embeddings"):
51 | mask = tf.to_float(tf.not_equal(x, 0))
52 | embeddings = tf.gather(self.shared_weight, tf.cast(x, tf.int64))
53 | embeddings *= tf.expand_dims(mask, -1)
54 |
55 | embeddings *= self.hidden_size ** 0.5 # scale embedding by the sqrt of the hidden size
56 | return embeddings
57 |
58 |
59 | def position_encoding(length, hidden_size, min_timescale=1.0, max_timescale=1.0e4):
60 | """
61 | Calculate the position encoding as a mix of sine and cosine function with geometrically
62 | increasing wavelengths.
63 | Defined and formulized in Attention is all your need.
64 | Args:
65 | length: sequence lenght
66 | hidden_size: size of the embedding
67 | min_timescale: Minimum scale that will be applied at each position
68 | max_timescale: Maximum scale that will be applied at each position
69 |
70 | Returns:
71 | Tensor with shape [length, hidden_size]
72 |
73 | """
74 | position = tf.cast(tf.range(length), tf.float32)
75 | num_timescales = hidden_size // 2
76 | log_timescale_increment = (math.log(float(max_timescale) / float(min_timescale)) /
77 | (tf.cast(num_timescales, tf.float32) -1))
78 | inv_timescales = min_timescale * tf.exp(
79 | tf.cast(tf.range(num_timescales), tf.float32) * -log_timescale_increment) # shape= [hidden_size/2]
80 | scaled_time = tf.expand_dims(position, 1) * tf.expand_dims(inv_timescales, 0) # shape= [length, hidden_size/2]
81 | signal = tf.concat([tf.sin(scaled_time), tf.cos(scaled_time)], axis=1) # shape= [length, hidden_size]
82 |
83 | return signal
84 |
85 |
86 | class Attention(tf.layers.Layer):
87 | """Multi-headed attention layer"""
88 | def __init__(self, hidden_size, num_head, attention_dropout, train):
89 | if hidden_size % num_head != 0:
90 | raise ValueError("Hidden_size must be evenly divisible by the number of heads")
91 |
92 | super(Attention, self).__init__()
93 | self.hidden_size = hidden_size
94 | self.num_head = num_head
95 | self.attention_dropout = attention_dropout
96 | self.train = train
97 |
98 | # Layer for linear projecting query, key, values:
99 | self.q_dense_layer = tf.layers.Dense(units=hidden_size, use_bias=False, name='q')
100 | self.k_dense_layer = tf.layers.Dense(units=hidden_size, use_bias=False, name='k')
101 | self.v_dense_layer = tf.layers.Dense(units=hidden_size, use_bias=False, name='v')
102 |
103 | self.output_dense_layer = tf.layers.Dense(units=hidden_size, use_bias=False, name='output_transformer')
104 |
105 | def split_heads(self, x):
106 | """
107 | Split x into different heads, and transpose the resulting value
108 | Args:
109 | x: A tensor with shape [batch, length, hidden_size]
110 |
111 | Returns:
112 | A tensor with shape[batch, num_head, length, hidden_size]
113 | """
114 | with tf.name_scope("split_heads"):
115 | batch_size, length = tf.shape(x)[0], tf.shape(x)[1]
116 |
117 | depth = (self.hidden_size // self.num_head)
118 |
119 | x = tf.reshape(x, [batch_size, length, self.num_head, depth])
120 |
121 | return tf.transpose(x, [0, 2, 1, 3])
122 |
123 | def combine_heads(self, x):
124 | """
125 | combine tensor that has been split
126 | Args:
127 | x: A tensor with shape [batch_size, num_heads, length, hidden_size/num_heads]
128 |
129 | Returns:
130 | A tensor with shape [batch, length, hidden_size]
131 | """
132 | with tf.name_scope("combine_heads"):
133 | batch_size, length = tf.shape(x)[0], tf.shape(x)[2]
134 | x = tf.transpose(x, [0, 2, 1, 3]) # --> [batch, length, num_heads, hidden_size/num_heads]
135 | return tf.reshape(x, [batch_size, length, self.hidden_size])
136 |
137 | def call(self, x, y, bias, cache=None):
138 | """
139 | Apply attention mechanism to x and y
140 | Args:
141 | x: A tensor with shape [batch, length_x, hidden_size]
142 | y: A tensor with shape [batch, length_y, hidden_size]
143 | bias: attention bias that will be add to the result of the dot product.
144 | cache: (Used during prediction) dictionary with tensor containing results of previus attentions.
145 | The dictionary must have the items:
146 | {'k': tensor with shape [batch, i, key_channels],
147 | 'v': tensor with shape [batch, i, value_channels]}
148 | where i is the current decoded length
149 |
150 | Returns:
151 | Attention layer output with shape [batch, length_x, hidden_size]
152 |
153 | """
154 | length = tf.shape(x)[1]
155 | q = self.q_dense_layer(x) # [batch, length, hidden_size]
156 | k = self.k_dense_layer(y)
157 | v = self.v_dense_layer(y)
158 |
159 | if cache is not None:
160 | # combine cached keys and values with new keys and values
161 | k = tf.concat([cache['k'], k], axis=1)
162 | v = tf.concat([cache['v'], v], axis=1)
163 |
164 | # update cache:
165 | cache['k'] = k
166 | cache['v'] = v
167 |
168 | # split q,k,v into heads:
169 | q = self.split_heads(q)
170 | k = self.split_heads(k)
171 | v = self.split_heads(v) # [batch_size, length, num_head, hidden_size//num_head]
172 |
173 | # scale q to prevent dot product between q and k from growing too large
174 | depth = self.hidden_size // self.num_head
175 | q *= depth ** -0.5
176 |
177 | # calculate dot product attention
178 | logits = tf.matmul(q, k, transpose_b=True)
179 |
180 | # add mask to prevent future words
181 | mask = create_look_ahead_mask(length)
182 | logits += mask
183 | weight = tf.nn.softmax(logits, name='attention_weigths')
184 | if self.train:
185 | weight = tf.nn.dropout(weight, keep_prob=1-self.attention_dropout)
186 | attention_output = tf.matmul(weight, v)
187 |
188 | # Recombine heads --> [batch, length, hidden_size]
189 | attention_output = self.combine_heads(attention_output)
190 |
191 | # Run the combined output through another linear projection layers
192 | attention_output = self.output_dense_layer(attention_output)
193 | return attention_output
194 |
195 |
196 | class SelfAttention(Attention):
197 | """
198 | multihead self-attention layer.
199 | """
200 | def call(self, x, bias, cache=None):
201 | return super(SelfAttention, self).call(x, x, bias, cache)
202 |
203 |
204 | class FeedForwardNetwork(tf.layers.Layer):
205 | """Fully connected feedforward network"""
206 | def __init__(self, hidden_size, filter_size, relu_dropout, train, allow_pad):
207 | super(FeedForwardNetwork, self).__init__()
208 | self.hidden_size = hidden_size
209 | self.filter_size = filter_size
210 | self.relu_dropout = relu_dropout
211 | self.train = train
212 | self.allow_pad = allow_pad
213 |
214 | self.filter_dense_layer = tf.layers.Dense(filter_size, use_bias=True, activation=tf.nn.relu,
215 | name='filter_layer')
216 | self.output_dense_layer = tf.layers.Dense(hidden_size, use_bias=True, name='output_layer')
217 |
218 | def call(self, x, padding=None):
219 | """
220 | Return outputs of the feedforward network"
221 | Args:
222 | x: Tensor with shape [batch_size, length, hidden_size]
223 | padding: Optional, if set, the padding values are temporatily removed from x.
224 | The padding values are placed back in the output tensor in the same locations.
225 | shape [batch, length]
226 | Returns:
227 | Output of the feed forward network
228 | shape [batch, length, hidden_size]
229 | """
230 | padding = None if not self.allow_pad else padding
231 | batch_size, length = tf.shape(x)[0], tf.shape(x)[1]
232 |
233 | if padding is not None:
234 | with tf.name_scope('remove_padding'):
235 | pad_mask = tf.reshape(padding, [-1, self.hidden_size])
236 |
237 | nonpad_ids = tf.to_int32(tf.where(pad_mask < 1e-9))
238 |
239 | # reshape x to [batch*length, hidden_size] to remove padding
240 | x = tf.reshape(x, shape=[-1, self.hidden_size])
241 | x = tf.gather_nd(x, indices=nonpad_ids)
242 |
243 | # Reshape x from 2 dimensions to 3 dimensions
244 | x.set_shape([None, self.hidden_size])
245 | x = tf.expand_dims(x, axis=0)
246 |
247 | output = self.filter_dense_layer(x)
248 | if self.train:
249 | output = tf.nn.dropout(output, keep_prob=1-self.relu_dropout)
250 | output = self.output_dense_layer(output)
251 |
252 | if padding is not None:
253 | with tf.name_scope('re_add_padding'):
254 | output = tf.squeeze(output, axis=0)
255 | output = tf.scatter_nd(indices=nonpad_ids, updates=output,
256 | shape=[batch_size*length, self.hidden_size])
257 | output = tf.reshape(output, [batch_size, length, self.hidden_size])
258 | return output
259 |
260 |
261 | class LayerNormalization(tf.layers.Layer):
262 | """
263 | Apply layer normalization
264 | """
265 | def __init__(self, hidden_size):
266 | super(LayerNormalization, self).__init__()
267 | self.hidden_size = hidden_size
268 |
269 | def build(self, _):
270 | self.scale = tf.get_variable(name='layer_norm_scale',
271 | shape=[self.hidden_size],
272 | initializer=tf.ones_initializer())
273 | self.bias = tf.get_variable(name='layer_norm_bias',
274 | shape=[self.hidden_size],
275 | initializer=tf.zeros_initializer())
276 | self.build = True
277 |
278 | def call(self, x, epsilon=1e-6):
279 | mean = tf.reduce_mean(x, axis=[-1], keep_dims=True)
280 | variance = tf.reduce_mean(tf.square(x - mean), axis=[-1], keepdims=True)
281 | norm_x = (x - mean) * tf.sqrt(variance + epsilon)
282 | return norm_x * self.scale + self.bias
283 |
284 |
285 | def get_padding(x, padding_value=0, dtype=tf.float32):
286 | """
287 |
288 | Args:
289 | x: int tensor with any shape
290 | padding_value: int value which padding value set
291 | dtype: The dtype of the return value
292 |
293 | Returns:
294 | float tensor with same shape as x containing value 0,1
295 | 0 means non-padding, 1 means padding
296 |
297 | """
298 | with tf.name_scope('padding'):
299 | return tf.cast(tf.equal(x, padding_value), dtype)
300 |
301 |
302 | def get_padding_bias(x):
303 | """
304 | calculate bias tensot from padding values in tensor
305 |
306 | bias tensor that is added to the pre-softmax multi-head attention logits,
307 | which has shape [batch_size, num_heads, length, length]
308 | The tensor is zero at non-padding locations, and -1e9(negtive infinity) at padding locations
309 | Args:
310 | x:int tensor with shape [batch_size, length]
311 | Returns:
312 | Attention bias tensor of shape [batch_size, 1, 1, length]
313 |
314 | """
315 | with tf.name_scope('attention_bias'):
316 | padding = get_padding(x)
317 | attention_bias = padding * -1e9
318 | attention_bias = tf.expand_dims(attention_bias, axis=1)
319 | return attention_bias
320 |
321 |
322 | def create_look_ahead_mask(length, dtype=tf.float32):
323 |
324 | """Calculate bias for decoder that maintains model's autoregressive property.
325 | Creates a tensor that masks out locations that correspond to illegal
326 | connections, so prediction at position i cannot draw information from future
327 | positions.
328 | Args:
329 | length: int length of sequences in batch.
330 | dtype: The dtype of the return value.
331 | Returns:
332 | float tensor of shape [1, 1, length, length]
333 | """
334 | neg_inf = -1e9
335 | with tf.name_scope("decoder_self_attention_bias"):
336 | valid_locs = tf.linalg.band_part(tf.ones([length, length], dtype=dtype),
337 | -1, 0)
338 | valid_locs = tf.reshape(valid_locs, [1, 1, length, length])
339 | decoder_bias = neg_inf * (1.0 - valid_locs)
340 | return decoder_bias
341 |
342 |
343 |
--------------------------------------------------------------------------------
/code/Transformer-KT/run_dkt.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:run_dkt.py
5 | @time:2019/8/1212:39 PM
6 | """
7 | from __future__ import absolute_import
8 | from __future__ import division
9 | from __future__ import print_function
10 |
11 | import tensorflow as tf
12 | import os
13 | from model_params import BASE_PARAMS
14 | from model import *
15 | from metrics import *
16 | from utils import *
17 |
18 |
19 |
20 | flags = tf.flags
21 | logging = tf.logging
22 |
23 | flags.DEFINE_string(name='train_data', default='../data/train.tfrecord',
24 | help='Path of training data ')
25 | flags.DEFINE_string(name='valid_data', default='../data/eval.tfrecord',
26 | help='Path of valid data')
27 | flags.DEFINE_string(name='predict_data', default='',
28 | help='Path of predict data')
29 | flags.DEFINE_string(name='saved_model', default='./saved_model', help='Path to save model')
30 | flags.DEFINE_string(name='output_dir', default='./output_dir', help='Path to save model')
31 | flags.DEFINE_integer(name='epoch', default=100, help='Num of epoch')
32 | flags.DEFINE_integer(name='batch_size', default=1, help='Num of batch')
33 | flags.DEFINE_bool(name="do_train", default=True, help="Whether to run training.")
34 | flags.DEFINE_bool(name="do_eval", default=True, help="Whether to run eval on the dev set.")
35 | flags.DEFINE_bool(
36 | "do_predict", False,
37 | "Whether to run the model in inference mode on the test set.")
38 | flags.DEFINE_bool(name='pb', default=True, help='')
39 | FLAGS = flags.FLAGS
40 |
41 |
42 | def model_fn_builder():
43 | def model_fn(features, labels, mode, params):
44 | """
45 | define how to train, evaluate and predict from the transfomer model.
46 | Args:
47 |
48 | mode:
49 | params:
50 |
51 | Returns:
52 |
53 | """
54 | inputs = features['inputs']
55 | target_ids = features['target_id']
56 |
57 | is_training = (mode == tf.estimator.ModeKeys.TRAIN)
58 |
59 | try:
60 | batch_size, length = get_shape_list(inputs, expected_rank=2)
61 | except ValueError:
62 | batch_size = 1
63 | length = get_shape_list(inputs, expected_rank=1)[0]
64 | inputs = tf.reshape(inputs, [batch_size, length])
65 |
66 | with tf.variable_scope('model'):
67 | # Build model
68 | model = DKT(params, is_training)
69 | logits = model(inputs, target_ids) # [batch, length, vocab_size]
70 |
71 | # when in prediction mode, the label/target is Bone, the model output is the prediction
72 | if mode == tf.estimator.ModeKeys.PREDICT:
73 | export_outputs = {'predict_output': tf.estimator.export.PredictOutput({"predict": tf.sigmoid(logits)})}
74 | output_spec = tf.estimator.EstimatorSpec(mode=mode,
75 | predictions={'predict': tf.sigmoid(logits)},
76 | export_outputs=export_outputs
77 | )
78 | else:
79 | # Calculate model loss
80 | seq_steps = features['seq_len']
81 | target_correct = features['target_correct']
82 |
83 | loss = dkt_loss(logits, target_correct, target_ids, seq_steps)
84 | record_dict = {}
85 | record_dict['minibatch_loss'] = loss
86 | # Save loss as named tensor will be logged with the logging hook
87 | tf.identity(loss, 'cross_entropy')
88 |
89 | if mode == tf.estimator.ModeKeys.EVAL:
90 | metric_dict = get_eval_metrics(logits, target_correct, target_ids, seq_steps)
91 | record_dict['accuracy'] = metric_dict['accuracy']
92 | record_scalars(record_dict)
93 | output_spec = tf.estimator.EstimatorSpec(mode=tf.estimator.ModeKeys.EVAL,
94 | loss=loss,
95 | predictions={'predict': tf.sigmoid(logits)},
96 | eval_metric_ops=metric_dict )
97 | else: # train
98 | # check whether restore from checkpoint
99 | tvars = tf.trainable_variables()
100 | initialized_variable_names = {}
101 |
102 | tf.logging.info("**** Trainable Variables ****")
103 | for var in tvars:
104 | init_string = ""
105 | if var.name in initialized_variable_names:
106 | init_string = ", *INIT_FROM_CKPT*"
107 | tf.logging.info(" name = %s, shape = %s%s", var.name, var.shape,
108 | init_string)
109 |
110 | train_op, metric_dict = get_train_op_and_metrics(loss, params)
111 | acc_metric = get_eval_metrics(logits, target_correct, target_ids, seq_steps)
112 | record_dict['accuracy'] = acc_metric['accuracy']
113 | record_dict['learning_rate'] = metric_dict['learning_rate']
114 | record_scalars(record_dict)
115 | output_spec = tf.estimator.EstimatorSpec(mode=tf.estimator.ModeKeys.TRAIN,
116 | loss=loss,
117 | train_op=train_op
118 | )
119 | return output_spec
120 | return model_fn
121 |
122 |
123 | def input_fn_builder(datadir, epoch):
124 | def input_fn():
125 | padded_shapes = {'inputs': [None],
126 | 'target_correct': [None],
127 | 'target_id': [None],
128 | 'ids': [None],
129 | 'correct': [None],
130 |
131 | 'seq_len': [],
132 | }
133 |
134 | dataset_train = get_dataset(datadir)
135 | dataset_train = dataset_train.repeat(epoch).shuffle(1000).padded_batch(FLAGS.batch_size,
136 | padded_shapes=padded_shapes)
137 |
138 | return dataset_train
139 |
140 | return input_fn
141 |
142 |
143 | def main():
144 | # if not FLAGS.do_train and not FLAGS.do_eval and not FLAGS.do_predict:
145 | # raise ValueError("At least one of `do_train`, `do_eval` or `do_predict' must be True.")
146 | params = BASE_PARAMS # param dict from model_params.py
147 | tf.gfile.MakeDirs(FLAGS.saved_model)
148 |
149 | run_config = tf.estimator.RunConfig(
150 | model_dir=FLAGS.saved_model,
151 | tf_random_seed=tf.set_random_seed([42]),
152 | # save_summary_step=100,
153 | save_checkpoints_steps=1000,
154 | # save_checkpoints_secs=600,
155 | session_config=None,
156 | keep_checkpoint_max=5,
157 | keep_checkpoint_every_n_hours=10000,
158 | log_step_count_steps=100,
159 | train_distribute=None
160 | )
161 | model_fn = model_fn_builder()
162 | estimator = tf.estimator.Estimator(
163 | model_fn=model_fn,
164 | model_dir=FLAGS.saved_model,
165 | config=run_config,
166 | params=params,
167 | warm_start_from=None
168 | )
169 | # train_input_fn = input_fn_builder(FLAGS.train_data, FLAGS.epoch)
170 | # train_spec = tf.estimator.TrainSpec(input_fn=train_input_fn,
171 | # max_steps=None)
172 | # eval_input_fn = input_fn_builder(FLAGS.valid_data, 1)
173 | # eval_spec = tf.estimator.EvalSpec(input_fn=eval_input_fn,
174 | # steps=None,
175 | # throttle_secs=60)
176 | # tf.estimator.train_and_evaluate(estimator,
177 | # train_spec=train_spec,
178 | # eval_spec=eval_spec)
179 | for epoch in range(FLAGS.epoch):
180 | if FLAGS.do_train:
181 | train_input_fn = input_fn_builder(FLAGS.train_data, 1)
182 |
183 | estimator.train(input_fn=train_input_fn)
184 | if FLAGS.do_eval:
185 | eval_input_fn = input_fn_builder(FLAGS.valid_data, 1)
186 | result = estimator.evaluate(input_fn=eval_input_fn)
187 |
188 | output_eval_file = os.path.join(FLAGS.output_dir, "eval_results.txt")
189 | with tf.gfile.GFile(output_eval_file, "w+") as writer:
190 | tf.logging.info("***** Eval results *****")
191 | for key in sorted(result.keys()):
192 | tf.logging.info(" %s = %s", key, str(result[key]))
193 | writer.write("%s = %s\n" % (key, str(result[key])))
194 |
195 |
196 | if __name__ == '__main__':
197 | tf.logging.set_verbosity(tf.logging.INFO)
198 | # flags.mark_flag_as_required("train_data")
199 | # flags.mark_flag_as_required("valid_data")
200 | # flags.mark_flag_as_required("saved_model")
201 |
202 | main()
203 |
--------------------------------------------------------------------------------
/code/Transformer-KT/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:utils.py
5 | @time:2019/8/125:04 PM
6 | """
7 | from __future__ import absolute_import
8 | from __future__ import division
9 | from __future__ import print_function
10 |
11 | import tensorflow as tf
12 | import collections
13 | import re
14 |
15 |
16 | def get_learning_rate(learning_rate, hidden_size, learning_rate_warmup_steps):
17 | """calculate learning rate with linear warmup and rsqrt decay"""
18 | with tf.name_scope('learning_rate'):
19 | warmup_steps = tf.to_float(learning_rate_warmup_steps)
20 | step = tf.to_float(tf.train.get_or_create_global_step())
21 |
22 | learning_rate *= (hidden_size ** -0.5)
23 | # Apply linear warmup
24 | learning_rate *= tf.minimum(1.0, step/warmup_steps)
25 | # Apply rsqrt decay
26 | learning_rate *= tf.rsqrt(tf.maximum(step, warmup_steps))
27 |
28 | tf.identity(learning_rate, "learning_rate")
29 | return learning_rate
30 |
31 |
32 | def get_train_op_and_metrics(loss, params):
33 | """Generate training op and metrics to save in tensorboard"""
34 | with tf.variable_scope('get_train_op'):
35 | learning_rate = get_learning_rate(
36 | learning_rate=params['learning_rate'],
37 | hidden_size=params['hidden_size'],
38 | learning_rate_warmup_steps=params['learning_rate_warmup_steps']
39 | )
40 | # create optimizer , Use lazyAdamOptimizer from TF contrib,which is faster than the TF core Adam operation
41 | optimizer = tf.contrib.opt.LazyAdamOptimizer(
42 | learning_rate=learning_rate,
43 | beta1=params['optimizer_adam_beta1'],
44 | beta2=params['optimizer_adam_beta2'],
45 | epsilon=params['optimizer_adam_epsilon']
46 | )
47 | # calculate and apply graph gradient using LazyAdamOptimizer
48 | global_step = tf.train.get_global_step()
49 | tvars = tf.trainable_variables()
50 | gradients = optimizer.compute_gradients(loss, tvars, colocate_gradients_with_ops=True)
51 | minimize_op = optimizer.apply_gradients(gradients,
52 | global_step=global_step,
53 | name='train')
54 | update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
55 | train_op = tf.group(minimize_op, update_ops)
56 |
57 | train_metrics = {'learning_rate': learning_rate,
58 | 'global_step': global_step}
59 | return train_op, train_metrics
60 |
61 |
62 | def record_scalars(metric_dict):
63 | for key, value in metric_dict.items():
64 | print('records_scalars', key)
65 | if key == 'accuracy':
66 | tf.summary.scalar(name=key, tensor=value[1])
67 | else:
68 | tf.summary.scalar(name=key, tensor=value)
69 |
70 |
71 | def get_assignment_map_from_checkpoint(tvars, init_checkpoint):
72 | """Compute the union of the current variables and checkpoint variables."""
73 | assignment_map = {}
74 | initialized_variable_names = {}
75 |
76 | name_to_variable = collections.OrderedDict()
77 | for var in tvars:
78 | name = var.name
79 | m = re.match("^(.*):\\d+$", name)
80 | if m is not None:
81 | name = m.group(1)
82 | name_to_variable[name] = var
83 |
84 | init_vars = tf.train.list_variables(init_checkpoint)
85 |
86 | assignment_map = collections.OrderedDict()
87 | for x in init_vars:
88 | (name, var) = (x[0], x[1])
89 | if name not in name_to_variable:
90 | continue
91 | assignment_map[name] = name
92 | initialized_variable_names[name] = 1
93 | initialized_variable_names[name + ":0"] = 1
94 |
95 | return (assignment_map, initialized_variable_names)
96 |
97 |
98 | def parse_exmp(serial_exmp):
99 | feats = tf.parse_single_example(serial_exmp, features={'inputs': tf.VarLenFeature(tf.float32),
100 | 'target_correct': tf.VarLenFeature(tf.int64),
101 | 'target_id': tf.VarLenFeature(tf.float32),
102 |
103 | 'correct': tf.VarLenFeature(tf.int64),
104 | 'id': tf.VarLenFeature(tf.float32),
105 |
106 | 'seq_len': tf.FixedLenFeature([], tf.int64)})
107 | inputs = tf.sparse_tensor_to_dense(feats['inputs']) # 使用VarLenFeature读入的是一个sparse_tensor,用该函数进行转换
108 | target_correct = tf.sparse_tensor_to_dense(feats['target_correct'])
109 | target_id = tf.sparse_tensor_to_dense(feats['target_id'])
110 |
111 | correct = tf.sparse_tensor_to_dense(feats['correct'])
112 | id = tf.sparse_tensor_to_dense(feats['id'])
113 |
114 | inputs = tf.cast(inputs, tf.int32)
115 | target_correct = tf.cast(target_correct, tf.float32)
116 | target_id = tf.cast(target_id, tf.int32)
117 |
118 | correct = tf.cast(correct, tf.float32)
119 | id = tf.cast(id, tf.int32)
120 |
121 | seq_len = tf.cast(feats['seq_len'], tf.int32)
122 |
123 | return {'inputs': inputs,
124 | 'target_correct': target_correct,
125 | 'target_id': target_id,
126 | 'ids': id,
127 | 'correct': correct,
128 | 'seq_len': seq_len}
129 |
130 |
131 | def get_dataset(fname):
132 | dataset = tf.data.TFRecordDataset(fname)
133 | return dataset.map(parse_exmp) # use padded_batch method if padding needed
134 |
--------------------------------------------------------------------------------
/code/data/Assistment/2009-2010_skill_builder_data.csv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sulingling123/Knowledge_Tracing/e47425c068066190c0a435dc1ba1ce2c2a9c56ec/code/data/Assistment/2009-2010_skill_builder_data.csv
--------------------------------------------------------------------------------
/code/data/csv2tfrecords.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | """
3 | @author:Zoe
4 | @file:csv2tfrecords.py
5 | @time:2019/10/85:43 PM
6 | """
7 | import numpy as np
8 | from collections import defaultdict
9 | import tensorflow as tf
10 | import random
11 | import csv
12 |
13 |
14 | class EDM_Processor(object):
15 | """Processor for the MultiNLI data set (GLUE version)."""
16 | def __init__(self):
17 | self.num_skill = None
18 | self.max_len = None
19 |
20 | def _read_tsv(self, dataset_path):
21 | """Reads a tab separated value file."""
22 | rows = []
23 | max_skill_num = 0
24 | max_num_problems = 0
25 | with open(dataset_path, "r") as csvfile:
26 | reader = csv.reader(csvfile, delimiter=',')
27 | for row in reader:
28 | rows.append(row)
29 | index = 0
30 | i = 0
31 | print("the number of rows is " + str(len(rows)))
32 | tuple_rows = []
33 | # turn list to tuple
34 | while (index < len(rows) - 1):
35 | problems_num = int(rows[index][0])
36 | tmp_max_skill = max(map(int, rows[index + 1]))
37 | if (tmp_max_skill > max_skill_num):
38 | max_skill_num = tmp_max_skill
39 | if (problems_num <= 2):
40 | index += 3
41 | else:
42 | if problems_num > max_num_problems:
43 | max_num_problems = problems_num
44 | tup = (rows[index], rows[index + 1], rows[index + 2])
45 | tuple_rows.append(tup)
46 | index += 3
47 | # shuffle the tuple
48 |
49 | random.shuffle(tuple_rows)
50 | print("The number of num_skill is ", max_skill_num+1)
51 | print("Finish reading data...")
52 | self.max_len, self.num_skill = max_num_problems, max_skill_num + 1
53 | return tuple_rows
54 |
55 | def get_train_examples(self, data_dir):
56 | """See base class."""
57 |
58 | return self._create_examples(
59 | self._read_tsv(data_dir))
60 |
61 | def get_dev_examples(self, data_dir):
62 | """See base class."""
63 | return self._create_examples(
64 | self._read_tsv(data_dir))
65 |
66 | # def get_test_examples(self, data_dir):
67 | # """See base class."""
68 | # return self._create_examples(
69 | # self._read_tsv(os.path.join(data_dir, "test_matched.tsv")), "test")
70 |
71 | def _create_examples(self, tuple_rows):
72 | """Creates examples for the training and dev sets."""
73 | seq_len = []
74 | inputs = []
75 | target_correct = []
76 | target_id = []
77 | ids = []
78 | correct = []
79 | for i in range(len(tuple_rows)):
80 | # inputs
81 | inputs.append([int(tuple_rows[i][1][j]) + int(tuple_rows[i][2][j]) * self.num_skill for j in range(len(tuple_rows[i][1]) - 1)])
82 | seq_len.append(int(tuple_rows[i][0][0]) - 1) # sequence
83 | target_id.append(list(map(lambda k: int(k), tuple_rows[i][1][1:])))
84 | target_correct.append(list(map(lambda k: int(k), tuple_rows[i][2][1:])))
85 |
86 | ids.append(list(map(lambda k: int(k), tuple_rows[i][1][:-1])))
87 | correct.append(list(map(lambda k: int(k), tuple_rows[i][2][:-1])))
88 |
89 | return np.array(inputs), np.array(target_id), np.array(target_correct), np.array(ids), np.array(correct), np.array(seq_len)
90 |
91 |
92 | def encode_tfrecord(tfrecords_filename, inputs, target_id, target_correct, ids, correct, seq_len):
93 | writer = tf.python_io.TFRecordWriter(tfrecords_filename)
94 | nums = inputs.shape[0]
95 | print('%s records' % nums)
96 | for i in range(nums):
97 | tfrecords_features = dict()
98 |
99 | tfrecords_features['inputs'] = tf.train.Feature(float_list=tf.train.FloatList(value=inputs[i]))
100 | tfrecords_features['target_id'] = tf.train.Feature(float_list=tf.train.FloatList(value=target_id[i]))
101 | tfrecords_features['target_correct'] = tf.train.Feature(int64_list=tf.train.Int64List(value=target_correct[i]))
102 | tfrecords_features['ids'] = tf.train.Feature(float_list=tf.train.FloatList(value=ids[i]))
103 | tfrecords_features['correct'] = tf.train.Feature(int64_list=tf.train.Int64List(value=target_id[i]))
104 | tfrecords_features['seq_len'] = tf.train.Feature(int64_list=tf.train.Int64List(value=[seq_len[i]]))
105 | example = tf.train.Example(features=tf.train.Features(feature=tfrecords_features))
106 | exmp_serial = example.SerializeToString()
107 | writer.write(exmp_serial)
108 | writer.close()
109 |
110 |
111 | if __name__ == '__main__':
112 |
113 | train_dir = '../data/2016-EDM/0910_b_train.csv'
114 | processor = EDM_Processor()
115 | inputs, target_id, target_correct, ids, correct, seq_len = processor.get_train_examples(train_dir)
116 | print('data shape:', inputs.shape, target_id.shape, target_correct.shape, seq_len.shape)
117 |
118 | encode_tfrecord('train.tfrecord', inputs, target_id, target_correct, ids, correct, seq_len)
119 |
120 | train_dir = '../data/2016-EDM/0910_b_test.csv'
121 | processor = EDM_Processor()
122 | inputs, target_id, target_correct, ids, correct, seq_len = processor.get_train_examples(train_dir)
123 | print('data shape:', inputs.shape, target_id.shape, target_correct.shape, seq_len.shape)
124 | encode_tfrecord('eval.tfrecord', inputs, target_id, target_correct, ids, correct, seq_len)
125 |
--------------------------------------------------------------------------------
/code/data/eval.tfrecord:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sulingling123/Knowledge_Tracing/e47425c068066190c0a435dc1ba1ce2c2a9c56ec/code/data/eval.tfrecord
--------------------------------------------------------------------------------
/code/data/train.tfrecord:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sulingling123/Knowledge_Tracing/e47425c068066190c0a435dc1ba1ce2c2a9c56ec/code/data/train.tfrecord
--------------------------------------------------------------------------------