jpcabangon commited on
Commit
535d03c
1 Parent(s): 17cc763

initial upload

Browse files
Files changed (8) hide show
  1. .gitattributes +1 -0
  2. 46_Knowledge-white4.png +0 -0
  3. app.py +168 -0
  4. data_reader.py +40 -0
  5. losses.py +28 -0
  6. model.py +134 -0
  7. requirements.txt +10 -0
  8. tf_model.ckpt +3 -0
.gitattributes CHANGED
@@ -32,3 +32,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
32
  *.zip filter=lfs diff=lfs merge=lfs -text
33
  *.zst filter=lfs diff=lfs merge=lfs -text
34
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
32
  *.zip filter=lfs diff=lfs merge=lfs -text
33
  *.zst filter=lfs diff=lfs merge=lfs -text
34
  *tfevents* filter=lfs diff=lfs merge=lfs -text
35
+ tf_model.ckpt filter=lfs diff=lfs merge=lfs -text
46_Knowledge-white4.png ADDED
app.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # To run streamlit, go to terminal and type: 'streamlit run app.py'
2
+ # Core Packages ###########################
3
+ import os
4
+ import math
5
+
6
+ import numpy as np
7
+ import streamlit as st
8
+ import pandas as pd
9
+
10
+ from model import BertLightningModel
11
+
12
+ #######################################################################################################################
13
+ current_path = os.path.abspath(os.path.dirname(__file__))
14
+
15
+ project_title = "Automatic Essay Scoring"
16
+ project_desc = "The Automatic Essay Scoring app is a tool that uses natural language processing and machine learning algorithms to automatically grade essays. " \
17
+ "This is an application of Microsoft's Deberta v3-large model."
18
+ project_icon = "46_Knowledge-white4.png"
19
+
20
+ project_link = "https://huggingface.co/microsoft/deberta-v3-large \n https://www.kaggle.com/code/yasufuminakama/fb3-deberta-v3-base-baseline-train/notebook"
21
+ st.set_page_config(page_title=project_title, initial_sidebar_state='collapsed',page_icon=project_icon)
22
+
23
+ # additional info from the readme
24
+ add_info_md = """
25
+
26
+ """
27
+ #######################################################################################################################
28
+ def load_model():
29
+ CONFIG = dict(
30
+ model_name="microsoft/deberta-v3-large",
31
+ num_classes=6,
32
+ lr=2e-5,
33
+
34
+ batch_size=8,
35
+ num_workers=8,
36
+ max_length=512,
37
+ weight_decay=0.01,
38
+
39
+ accelerator='gpu',
40
+ max_epochs=5,
41
+ accumulate_grad_batches=4,
42
+ precision=16,
43
+ gradient_clip_val=1000,
44
+ train_size=0.8,
45
+ num_cross_val_splits=5,
46
+ num_frozen_layers=20, # out of 24 in deberta
47
+ )
48
+ model = BertLightningModel.load_from_checkpoint(os.path.join(current_path,'tf_model.ckpt'),config=CONFIG, map_location='cpu')
49
+
50
+ return model
51
+
52
+ def predict(_input, _model):
53
+ tokens = _model.tokenizer([_input], return_tensors='pt')
54
+ outputs = _model(tokens)[0].tolist()
55
+
56
+ df = pd.DataFrame({
57
+ 'Criterion': ['cohesion', 'syntax', 'vocabulary', 'phraseology', 'grammar', 'conventions'],
58
+ 'Grade': outputs
59
+ })
60
+
61
+ return df
62
+
63
+ def convert_ave_to_score_range(score, max, min):
64
+ fg = (score-1) * ((max-min)/3) + min
65
+ return fg
66
+
67
+ def run_model(answer, min_score, max_score):
68
+ evaluation = 0
69
+ if len(answer) > 20:
70
+ model = load_model()
71
+ evaluation = predict(answer,model)
72
+ else:
73
+ st.error("Your answer is too short.")
74
+
75
+ # get the average of the score evaluations
76
+ ave = evaluation['Grade'].mean()
77
+
78
+ grade = convert_ave_to_score_range(ave,max_score,min_score)
79
+ grade = round(grade)
80
+ final_grade = max_score if max_score < grade else grade
81
+ return evaluation, final_grade
82
+
83
+ def main():
84
+ head_col = st.columns([1,8])
85
+ with head_col[0]:
86
+ st.image(project_icon)
87
+ with head_col[1]:
88
+ st.title(project_title)
89
+ st.write(project_desc)
90
+ st.write(f"Source Project: {project_link}")
91
+ # expander = st.expander("Additional Information")
92
+ # expander.markdown(add_info_md)
93
+ st.markdown("***")
94
+ st.subheader("")
95
+ #########################################
96
+ # instructions
97
+ st.subheader("How to use: \n"
98
+ "1. Input your essay in the text box.\n"
99
+ "2. Click on \'Grade Essay\' button to run the model.")
100
+
101
+ #########################################
102
+
103
+ # text area input for the prompt and response, button to run the model, other widgets
104
+ response_ta = st.text_area("Essay:",placeholder="Input the answer / essay here.",height=500)
105
+ col1,col2,col3 = st.columns(3)
106
+ min_score = col1.number_input('Minimum Score',0,100,0)
107
+ max_score = col2.number_input('Maximum Score',0,100,10)
108
+ run_button = st.button("Grade Essay")
109
+
110
+ # button is clicked
111
+ if run_button:
112
+ if not response_ta: # if any text area is empty:
113
+ st.error("Please input the essay in their corresponding text area.")
114
+ if min_score >= max_score:
115
+ st.error("Minimum score must be less than maximum score.")
116
+ else: # run model
117
+ eval_df, score = run_model(answer=response_ta, min_score=min_score, max_score=max_score)
118
+ # output message template
119
+ msg = f"Your essay score is: {score} (Minimum Possible Score: {min_score} | Maximum Possible Score: {max_score})"
120
+ st.write(msg)
121
+ st.write("Score breakdown (1-4):")
122
+ st.dataframe(eval_df)
123
+
124
+ #########################################
125
+ # examples section
126
+ st.subheader("")
127
+ st.markdown("***")
128
+ st.subheader("")
129
+
130
+ # # generate examples dropdown
131
+ # # st.subheader("Here are some example prompts and responses for you to try:")
132
+ # examples = []
133
+ # selected_example = st.selectbox('Select an example prompt:',examples)
134
+ #
135
+ # # default minimum and maximum scores for each example
136
+ # mins_ex = [2,1,0,0,0,0,0,0]
137
+ # maxes_ex = [12,6,3,3,4,4,30,60]
138
+ #
139
+ # # widgets and button to run on examples
140
+ # prompt_ta_ex = st.text_area("Essay Prompt:",placeholder="Input the question or the essay prompt here.", value=selected_example,key='prompt_ta_ex',height=175)
141
+ # response_ta_ex = st.text_area("Essay Response:",placeholder="Input the answer / essay response here.",key='response_ta_ex',height=250)
142
+ # col1_ex, col2_ex, col3_ex = st.columns(3)
143
+ # selected_min = mins_ex[examples.index(selected_example)]
144
+ # selected_max = maxes_ex[examples.index(selected_example)]
145
+ # min_score_ex = col1_ex.number_input('Minimum Score',0,100,key='min_score_ex',value=selected_min)
146
+ # max_score_ex = col2_ex.number_input('Maximum Score',0,100,key='max_score_ex',value=selected_max)
147
+ # passing_rate_ex = col3_ex.number_input('Passing Rate',0.0,1.0,0.6,step=0.05,key='passing_rate_ex')
148
+ # run_button_ex = st.button("Grade Example Essay")
149
+ #
150
+ # # button is clicked
151
+ # if run_button_ex:
152
+ # if not prompt_ta_ex or not response_ta_ex: # if any text area is empty:
153
+ # st.error("Please input the prompt / response in their corresponding text area.")
154
+ # else: # run model
155
+ # score, passed = run_model(answer=response_ta_ex, min_score=min_score_ex, max_score=max_score_ex, passing_rate=passing_rate_ex)
156
+ # # output message template
157
+ # msg_ex = f"Your essay score is: {int(score)} (Minimum Possible Score: {min_score_ex} | Maximum Possible Score: {max_score_ex})"
158
+ #
159
+ # if passed: # check if score achieved passes the threshold
160
+ # st.success(f"Congratulations! {msg_ex}")
161
+ # if not passed:
162
+ # st.warning(msg_ex)
163
+
164
+
165
+ if __name__ == '__main__':
166
+ main()
167
+
168
+ # To run streamlit, go to terminal and type: 'streamlit run app-source.py'
data_reader.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Tuple
4
+
5
+ import pandas as pd
6
+ # from dotenv import load_dotenv
7
+ #
8
+ # load_dotenv()
9
+
10
+
11
+ # def set_env_if_kaggle_environ() -> None:
12
+ # if 'KAGGLE_DATA_PROXY_TOKEN' in os.environ:
13
+ # os.environ['DATA_PATH'] = '/kaggle/input/feedback-prize-english-language-learning/'
14
+
15
+
16
+ def load_train_test_df(is_testing: bool = False) -> Tuple[pd.DataFrame, pd.DataFrame]:
17
+ """Loads train/test dataframes
18
+ :param is_testing: If set to true, load subsample of train/test dataframes
19
+ :return Train and test dataframes
20
+ """
21
+ # set_env_if_kaggle_environ()
22
+
23
+ if is_testing:
24
+ train_df_path = Path("tests/data/train_sample.csv")
25
+ test_df_path = Path("tests/data/test_sample.csv")
26
+
27
+ else:
28
+ train_df_path = Path(os.environ['DATA_PATH']) / 'train.csv'
29
+ test_df_path = Path(os.environ['DATA_PATH']) / 'test.csv'
30
+
31
+ if not test_df_path.is_file():
32
+ raise OSError(f"File not found: {test_df_path.absolute()}")
33
+
34
+ if not train_df_path.is_file():
35
+ raise OSError(f"File not found: {train_df_path.absolute()}")
36
+
37
+ train_df = pd.read_csv(train_df_path)
38
+ test_df = pd.read_csv(test_df_path)
39
+
40
+ return train_df, test_df
losses.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from torch import Tensor
4
+
5
+
6
+ class MCRMSELoss(nn.Module):
7
+
8
+ def __init__(self):
9
+ super(MCRMSELoss, self).__init__()
10
+ self.mse = nn.MSELoss(reduction='none')
11
+
12
+ def forward(self, y_pred: Tensor, y_true: Tensor):
13
+ """Calculate mean column-wise rmse on columns
14
+ :param y_pred: tensor of shape (bs, 6)
15
+ :param y_true: tensor of shape (bs, 6)
16
+ :return: tensor of shape 0 (scalar with grad)
17
+ """
18
+
19
+ mse = self.mse(y_pred, y_true).mean(0) # column-wise mean
20
+ rmse = torch.sqrt(mse + 1e-7)
21
+
22
+ return rmse.mean()
23
+
24
+ def class_mcrmse(self, y_pred: Tensor, y_true: Tensor):
25
+ mse = self.mse(y_pred, y_true).mean(0) # column-wise mean
26
+ rmse = torch.sqrt(mse + 1e-7)
27
+
28
+ return rmse.squeeze()
model.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Copy pasted model from https://www.kaggle.com/code/yasufuminakama/fb3-deberta-v3-base-baseline-train/notebook
3
+ """
4
+ import pytorch_lightning as pl
5
+ import torch
6
+ import torch.nn as nn
7
+ from torch.optim import AdamW
8
+ from torch.optim.lr_scheduler import CosineAnnealingLR
9
+ from transformers import AutoConfig, AutoModel, AutoTokenizer
10
+
11
+ from data_reader import load_train_test_df
12
+ from losses import MCRMSELoss
13
+
14
+
15
+ def num_train_samples():
16
+ train_df, _ = load_train_test_df()
17
+ return len(train_df)
18
+
19
+
20
+ class MeanPooling(nn.Module):
21
+ # taking mean of last hidden state with mask
22
+
23
+ def forward(self, last_hidden_state, attention_mask):
24
+ input_mask_expanded = attention_mask.unsqueeze(-1).expand(last_hidden_state.size()).float()
25
+ sum_embeddings = torch.sum(last_hidden_state * input_mask_expanded, 1)
26
+ sum_mask = input_mask_expanded.sum(1)
27
+ sum_mask = torch.clamp(sum_mask, min=1e-9)
28
+ mean_embeddings = sum_embeddings / sum_mask
29
+ return mean_embeddings
30
+
31
+
32
+ class BertLightningModel(pl.LightningModule):
33
+
34
+ def __init__(self, config: dict):
35
+ super(BertLightningModel, self).__init__()
36
+
37
+ self.config = config
38
+
39
+ huggingface_config = AutoConfig.from_pretrained(self.config['model_name'], output_hidden_states=True)
40
+ huggingface_config.hidden_dropout = 0.
41
+ huggingface_config.hidden_dropout_prob = 0.
42
+ huggingface_config.attention_dropout = 0.
43
+ huggingface_config.attention_probs_dropout_prob = 0.
44
+
45
+ self.tokenizer = AutoTokenizer.from_pretrained(self.config['model_name'])
46
+ self.model = AutoModel.from_pretrained(self.config['model_name'], config=huggingface_config)
47
+
48
+ self.pool = MeanPooling()
49
+
50
+ self.fc = nn.Linear(in_features=1024, out_features=6)
51
+
52
+ self.loss = MCRMSELoss()
53
+
54
+ # freezing first 20 layers of DeBERTa from 24
55
+ modules = [self.model.embeddings, self.model.encoder.layer[:self.config['num_frozen_layers']]]
56
+ for module in modules:
57
+ for param in module.parameters():
58
+ param.requires_grad = False
59
+
60
+ self.class_metric = None
61
+ self.best_metric = None
62
+
63
+ def forward(self, inputs):
64
+ outputs = self.model(**inputs)
65
+ last_hidden_state = outputs.last_hidden_state
66
+
67
+ bert_features = self.pool(last_hidden_state, inputs['attention_mask'])
68
+
69
+ logits = self.fc(bert_features)
70
+
71
+ return logits
72
+
73
+ def training_step(self, batch, batch_idx):
74
+ inputs = batch
75
+ labels = inputs.pop("labels", None)
76
+ logits = self(inputs)
77
+ loss = self.loss(logits, labels)
78
+
79
+ self.log('train/loss', loss)
80
+
81
+ return {
82
+ 'loss': loss,
83
+ 'mc_rmse': loss
84
+ }
85
+
86
+ def training_epoch_end(self, outputs):
87
+ mean_mc_rmse = sum(output['mc_rmse'].item() for output in outputs) / len(outputs)
88
+ self.log("train/epoch_loss", mean_mc_rmse)
89
+
90
+ def validation_step(self, batch, batch_idx):
91
+ inputs = batch
92
+ labels = inputs.pop("labels", None)
93
+ logits = self(inputs)
94
+ loss = self.loss(logits, labels)
95
+ class_rmse = self.loss.class_mcrmse(logits, labels)
96
+
97
+ self.log('val/loss', loss)
98
+
99
+ return {
100
+ 'loss': loss,
101
+ 'mc_rmse': loss,
102
+ 'class_mc_rmse': class_rmse
103
+ }
104
+
105
+ def validation_epoch_end(self, outputs):
106
+ mean_mc_rmse = sum(output['mc_rmse'].item() for output in outputs) / len(outputs)
107
+ class_metrics = torch.stack([output['class_mc_rmse'] for output in outputs]).mean(0).tolist()
108
+ class_metrics = [round(item, 4) for item in class_metrics]
109
+ self.log('val/epoch_loss', mean_mc_rmse)
110
+
111
+ if self.best_metric is None or mean_mc_rmse < self.best_metric:
112
+ self.best_metric = mean_mc_rmse
113
+ self.class_metric = class_metrics
114
+
115
+ def configure_optimizers(self):
116
+ # weight_decay = self.config['weight_decay']
117
+ lr = self.config['lr']
118
+
119
+ # In original solution authors add weight decaying to some parameters
120
+
121
+ optimizer = AdamW(self.parameters(), lr=lr, weight_decay=0.0, eps=1e-6, betas=(0.9, 0.999))
122
+
123
+ scheduler = CosineAnnealingLR(
124
+ optimizer,
125
+ T_max=self.config['max_epochs'],
126
+ )
127
+ return [optimizer], [scheduler]
128
+
129
+ def predict_step(self, batch, batch_idx: int, dataloader_idx: int = 0):
130
+ inputs = batch
131
+ inputs.pop("labels", None)
132
+ logits = self(inputs)
133
+
134
+ return logits
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit==1.16.0
2
+ numpy==1.21.6
3
+ pandas==1.3.5
4
+ torch==1.12.1
5
+ transformers==4.22.2
6
+ scikit-learn==1.0.2
7
+ nltk==3.7
8
+ seaborn==0.12.1
9
+ pytorch-lightning==1.8.3
10
+ sentencepiece==0.1.97
tf_model.ckpt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4c22d4ab171b1e8594cac4e4f8030010d0fcad9c69caf5c7036a4f6650d82330
3
+ size 1736236975