jpcabangon
commited on
Commit
•
535d03c
1
Parent(s):
17cc763
initial upload
Browse files- .gitattributes +1 -0
- 46_Knowledge-white4.png +0 -0
- app.py +168 -0
- data_reader.py +40 -0
- losses.py +28 -0
- model.py +134 -0
- requirements.txt +10 -0
- 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
|