Skip to content

Commit

Permalink
fixes #3762
Browse files Browse the repository at this point in the history
  • Loading branch information
jph00 committed Aug 1, 2022
1 parent fb3ddd2 commit 0454367
Show file tree
Hide file tree
Showing 14 changed files with 431 additions and 119 deletions.
8 changes: 4 additions & 4 deletions README.md
Expand Up @@ -69,10 +69,10 @@ segmentation model, a text sentiment model, a recommendation system, and
a tabular model. For each of the applications, the code is much the
same.

Read through the [Tutorials](https://docs.fast.ai/tutorial) to learn how
to train your own models on your own datasets. Use the navigation
sidebar to look through the fastai documentation. Every class, function,
and method is documented here.
Read through the [Tutorials](https://docs.fast.ai/tutorial.html) to
learn how to train your own models on your own datasets. Use the
navigation sidebar to look through the fastai documentation. Every
class, function, and method is documented here.

To learn about the design and motivation of the library, read the [peer
reviewed paper](https://www.mdpi.com/2078-2489/11/2/108/htm).
Expand Down
4 changes: 2 additions & 2 deletions fastai/_modidx.py
Expand Up @@ -42,8 +42,7 @@
'fastai.callback.captum': { 'fastai.callback.captum.CaptumInterpretation': 'https://docs.fast.ai/callback.captum.html#captuminterpretation',
'fastai.callback.captum.CaptumInterpretation.get_baseline_img': 'https://docs.fast.ai/callback.captum.html#captuminterpretation.get_baseline_img',
'fastai.callback.captum.CaptumInterpretation.insights': 'https://docs.fast.ai/callback.captum.html#captuminterpretation.insights',
'fastai.callback.captum.CaptumInterpretation.visualize': 'https://docs.fast.ai/callback.captum.html#captuminterpretation.visualize',
'fastai.callback.captum.json_clean': 'https://docs.fast.ai/callback.captum.html#json_clean'},
'fastai.callback.captum.CaptumInterpretation.visualize': 'https://docs.fast.ai/callback.captum.html#captuminterpretation.visualize'},
'fastai.callback.comet': { 'fastai.callback.comet.CometCallback': 'https://docs.fast.ai/callback.comet.html#cometcallback',
'fastai.callback.comet.CometCallback.after_batch': 'https://docs.fast.ai/callback.comet.html#cometcallback.after_batch',
'fastai.callback.comet.CometCallback.after_epoch': 'https://docs.fast.ai/callback.comet.html#cometcallback.after_epoch',
Expand Down Expand Up @@ -1286,6 +1285,7 @@
'fastai.vision.core.BBoxLabeler.setups': 'https://docs.fast.ai/vision.core.html#bboxlabeler.setups',
'fastai.vision.core.BILINEAR': 'https://docs.fast.ai/vision.core.html#bilinear',
'fastai.vision.core.Image': 'https://docs.fast.ai/vision.core.html#image',
'fastai.vision.core.Image.Image.__repr__': 'https://docs.fast.ai/vision.core.html#image.image.__repr__',
'fastai.vision.core.Image.Image.aspect': 'https://docs.fast.ai/vision.core.html#image.image.aspect',
'fastai.vision.core.Image.Image.n_px': 'https://docs.fast.ai/vision.core.html#image.image.n_px',
'fastai.vision.core.Image.Image.reshape': 'https://docs.fast.ai/vision.core.html#image.image.reshape',
Expand Down
4 changes: 2 additions & 2 deletions fastai/callback/captum.py
Expand Up @@ -6,7 +6,7 @@
from ..basics import *

# %% auto 0
__all__ = ['json_clean', 'CaptumInterpretation']
__all__ = ['CaptumInterpretation']

# %% ../nbs/70c_callback.captum.ipynb 6
from ipykernel import jsonutil
Expand Down Expand Up @@ -92,7 +92,7 @@ def _get_attributions(self,enc_data,metric,n_steps,nt_type,baseline_type,strides
sliding_window_shapes=sliding_window_shapes,
baselines=baseline)

# %% ../nbs/70c_callback.captum.ipynb 25
# %% ../nbs/70c_callback.captum.ipynb 26
@patch
def insights(x: CaptumInterpretation,inp_data,debug=True):
_baseline_func= lambda o: o*0
Expand Down
28 changes: 14 additions & 14 deletions fastai/callback/tensorboard.py
Expand Up @@ -8,13 +8,13 @@
__all__ = ['TensorBoardBaseCallback', 'TensorBoardCallback', 'TensorBoardProjectorCallback', 'projector_word_embeddings',
'tensorboard_log']

# %% ../nbs/70a_callback.tensorboard.ipynb 18
# %% ../nbs/70a_callback.tensorboard.ipynb 19
import tensorboard
from torch.utils.tensorboard import SummaryWriter
from .fp16 import ModelToHalf
from .hook import hook_output

# %% ../nbs/70a_callback.tensorboard.ipynb 19
# %% ../nbs/70a_callback.tensorboard.ipynb 20
class TensorBoardBaseCallback(Callback):
order = Recorder.order+1
"Base class for tensorboard callbacks"
Expand Down Expand Up @@ -42,7 +42,7 @@ def __del__(self): self._remove()
def _remove(self):
if getattr(self, 'h', None): self.h.remove()

# %% ../nbs/70a_callback.tensorboard.ipynb 20
# %% ../nbs/70a_callback.tensorboard.ipynb 22
class TensorBoardCallback(TensorBoardBaseCallback):
"Saves model topology, losses & metrics for tensorboard and tensorboard projector during training"
def __init__(self, log_dir=None, trace_model=True, log_preds=True, n_preds=9, projector=False, layer=None):
Expand Down Expand Up @@ -79,7 +79,7 @@ def after_epoch(self):
def before_validate(self):
if self.projector: self._setup_projector()

# %% ../nbs/70a_callback.tensorboard.ipynb 21
# %% ../nbs/70a_callback.tensorboard.ipynb 24
class TensorBoardProjectorCallback(TensorBoardBaseCallback):
"Extracts and exports image featuers for tensorboard projector during inference"
def __init__(self, log_dir=None, layer=None):
Expand All @@ -94,13 +94,13 @@ def before_fit(self):
def before_validate(self):
self._setup_projector()

# %% ../nbs/70a_callback.tensorboard.ipynb 22
# %% ../nbs/70a_callback.tensorboard.ipynb 26
def _write_projector_embedding(learn, writer, feat):
lbls = [learn.dl.vocab[l] for l in feat['lbl']] if getattr(learn.dl, 'vocab', None) else None
vecs = feat['vec'].squeeze()
writer.add_embedding(vecs, metadata=lbls, label_img=feat['img'], global_step=learn.train_iter)

# %% ../nbs/70a_callback.tensorboard.ipynb 23
# %% ../nbs/70a_callback.tensorboard.ipynb 27
def _add_projector_features(learn, hook, feat):
img = _normalize_for_projector(learn.x)
first_epoch = True if learn.iter == 0 else False
Expand All @@ -110,12 +110,12 @@ def _add_projector_features(learn, hook, feat):
feat['lbl'] = learn.y if first_epoch else torch.cat((feat['lbl'], learn.y),0)
return feat

# %% ../nbs/70a_callback.tensorboard.ipynb 24
# %% ../nbs/70a_callback.tensorboard.ipynb 28
def _get_embeddings(model, layer):
layer = model[0].encoder if layer == None else layer
return layer.weight

# %% ../nbs/70a_callback.tensorboard.ipynb 25
# %% ../nbs/70a_callback.tensorboard.ipynb 29
@typedispatch
def _normalize_for_projector(x:TensorImage):
# normalize tensor to be between 0-1
Expand All @@ -127,10 +127,10 @@ def _normalize_for_projector(x:TensorImage):
img = img.view(*sz)
return img

# %% ../nbs/70a_callback.tensorboard.ipynb 26
# %% ../nbs/70a_callback.tensorboard.ipynb 30
from ..text.all import LMLearner, TextLearner

# %% ../nbs/70a_callback.tensorboard.ipynb 27
# %% ../nbs/70a_callback.tensorboard.ipynb 31
def projector_word_embeddings(learn=None, layer=None, vocab=None, limit=-1, start=0, log_dir=None):
"Extracts and exports word embeddings from language models embedding layers"
if not layer:
Expand All @@ -145,10 +145,10 @@ def projector_word_embeddings(learn=None, layer=None, vocab=None, limit=-1, star
writer.add_embedding(emb[start:end], metadata=vocab[start:end], label_img=img[start:end])
writer.close()

# %% ../nbs/70a_callback.tensorboard.ipynb 28
# %% ../nbs/70a_callback.tensorboard.ipynb 33
from ..vision.data import *

# %% ../nbs/70a_callback.tensorboard.ipynb 29
# %% ../nbs/70a_callback.tensorboard.ipynb 34
@typedispatch
def tensorboard_log(x:TensorImage, y: TensorCategory, samples, outs, writer, step):
fig,axs = get_grid(len(samples), return_fig=True)
Expand All @@ -158,10 +158,10 @@ def tensorboard_log(x:TensorImage, y: TensorCategory, samples, outs, writer, ste
for b,r,c in zip(samples.itemgot(1),outs.itemgot(0),axs)]
writer.add_figure('Sample results', fig, step)

# %% ../nbs/70a_callback.tensorboard.ipynb 30
# %% ../nbs/70a_callback.tensorboard.ipynb 35
from ..vision.core import TensorPoint,TensorBBox

# %% ../nbs/70a_callback.tensorboard.ipynb 31
# %% ../nbs/70a_callback.tensorboard.ipynb 36
@typedispatch
def tensorboard_log(x:TensorImage, y: TensorImageBase|TensorPoint|TensorBBox, samples, outs, writer, step):
fig,axs = get_grid(len(samples), return_fig=True, double=True)
Expand Down
28 changes: 14 additions & 14 deletions fastai/callback/wandb.py
Expand Up @@ -157,7 +157,7 @@ def after_fit(self):
self._wandb_step += 1


# %% ../nbs/70_callback.wandb.ipynb 11
# %% ../nbs/70_callback.wandb.ipynb 12
@patch
def gather_args(self:Learner):
"Gather config parameters accessible to the learner"
Expand Down Expand Up @@ -185,7 +185,7 @@ def gather_args(self:Learner):
args['dls.after_batch'] = f'{self.dls.after_batch}'
return args

# %% ../nbs/70_callback.wandb.ipynb 12
# %% ../nbs/70_callback.wandb.ipynb 14
def _make_plt(img):
"Make plot to image resolution"
# from https://stackoverflow.com/a/13714915
Expand All @@ -198,15 +198,15 @@ def _make_plt(img):
fig.add_axes(ax)
return fig, ax

# %% ../nbs/70_callback.wandb.ipynb 13
# %% ../nbs/70_callback.wandb.ipynb 15
def _format_config_value(v):
if isinstance(v, list):
return [_format_config_value(item) for item in v]
elif hasattr(v, '__stored_args__'):
return {**_format_config(v.__stored_args__), '_name': v}
return v

# %% ../nbs/70_callback.wandb.ipynb 14
# %% ../nbs/70_callback.wandb.ipynb 16
def _format_config(config):
"Format config parameters before logging them"
for k,v in config.items():
Expand All @@ -216,12 +216,12 @@ def _format_config(config):
config[k] = _format_config_value(v)
return config

# %% ../nbs/70_callback.wandb.ipynb 15
# %% ../nbs/70_callback.wandb.ipynb 17
def _format_metadata(metadata):
"Format metadata associated to artifacts"
for k,v in metadata.items(): metadata[k] = str(v)

# %% ../nbs/70_callback.wandb.ipynb 16
# %% ../nbs/70_callback.wandb.ipynb 18
def log_dataset(path, name=None, metadata={}, description='raw dataset'):
"Log dataset folder"
# Check if wandb.init has been called in case datasets are logged manually
Expand All @@ -240,7 +240,7 @@ def log_dataset(path, name=None, metadata={}, description='raw dataset'):
else: artifact_dataset.add_file(str(p.resolve()))
wandb.run.use_artifact(artifact_dataset)

# %% ../nbs/70_callback.wandb.ipynb 17
# %% ../nbs/70_callback.wandb.ipynb 20
def log_model(path, name=None, metadata={}, description='trained model'):
"Log model file"
if wandb.run is None:
Expand All @@ -255,7 +255,7 @@ def log_model(path, name=None, metadata={}, description='trained model'):
fa.write(path.read_bytes())
wandb.run.log_artifact(artifact_model)

# %% ../nbs/70_callback.wandb.ipynb 18
# %% ../nbs/70_callback.wandb.ipynb 22
@typedispatch
def wandb_process(x:TensorImage, y, samples, outs, preds):
"Process `sample` and `out` depending on the type of `x/y`"
Expand All @@ -272,22 +272,22 @@ def wandb_process(x:TensorImage, y, samples, outs, preds):
plt.close(fig)
return {"Inputs":res_input, "Predictions":res_pred, "Ground_Truth":res_label}

# %% ../nbs/70_callback.wandb.ipynb 19
# %% ../nbs/70_callback.wandb.ipynb 23
def _unlist(l):
"get element of lists of lenght 1"
if isinstance(l, (list, tuple)):
if len(l) == 1: return l[0]
else: return l

# %% ../nbs/70_callback.wandb.ipynb 20
# %% ../nbs/70_callback.wandb.ipynb 24
@typedispatch
def wandb_process(x:TensorImage, y:TensorCategory|TensorMultiCategory, samples, outs, preds):
table = wandb.Table(columns=["Input image", "Ground_Truth", "Predictions"])
for (image, label), pred_label in zip(samples,outs):
table.add_data(wandb.Image(image.permute(1,2,0)), label, _unlist(pred_label))
return {"Prediction_Samples": table}

# %% ../nbs/70_callback.wandb.ipynb 21
# %% ../nbs/70_callback.wandb.ipynb 25
@typedispatch
def wandb_process(x:TensorImage, y:TensorMask, samples, outs, preds):
res = []
Expand All @@ -305,18 +305,18 @@ def wandb_process(x:TensorImage, y:TensorMask, samples, outs, preds):
)
return {"Prediction_Samples": table}

# %% ../nbs/70_callback.wandb.ipynb 22
# %% ../nbs/70_callback.wandb.ipynb 26
@typedispatch
def wandb_process(x:TensorText, y:TensorCategory|TensorMultiCategory, samples, outs, preds):
data = [[s[0], s[1], o[0]] for s,o in zip(samples,outs)]
return {"Prediction_Samples": wandb.Table(data=data, columns=["Text", "Target", "Prediction"])}

# %% ../nbs/70_callback.wandb.ipynb 23
# %% ../nbs/70_callback.wandb.ipynb 27
@typedispatch
def wandb_process(x:Tabular, y:Tabular, samples, outs, preds):
df = x.all_cols
for n in x.y_names: df[n+'_pred'] = y[n].values
return {"Prediction_Samples": wandb.Table(dataframe=df)}

# %% ../nbs/70_callback.wandb.ipynb 27
# %% ../nbs/70_callback.wandb.ipynb 32
_all_ = ['wandb_process']
6 changes: 6 additions & 0 deletions fastai/vision/core.py
Expand Up @@ -6,6 +6,7 @@
from ..data.all import *

from PIL import Image

try: BILINEAR,NEAREST = Image.Resampling.BILINEAR,Image.Resampling.NEAREST
except AttributeError: from PIL.Image import BILINEAR,NEAREST

Expand All @@ -21,6 +22,11 @@
# %% ../nbs/07_vision.core.ipynb 7
_all_ = ['Image','ToTensor']

# %% ../nbs/07_vision.core.ipynb 8
@patch
def __repr__(x:Image.Image):
return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % (x.__class__.__module__, x.__class__.__name__, x.mode, x.size[0], x.size[1])

# %% ../nbs/07_vision.core.ipynb 11
imagenet_stats = ([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
cifar_stats = ([0.491, 0.482, 0.447], [0.247, 0.243, 0.261])
Expand Down
14 changes: 7 additions & 7 deletions fastai/vision/utils.py
Expand Up @@ -23,7 +23,7 @@ def _get_downloaded_image_filename(dest, name, suffix):

return candidate_name

# %% ../nbs/09b_vision.utils.ipynb 8
# %% ../nbs/09b_vision.utils.ipynb 7
def _download_image_inner(dest, inp, timeout=4, preserve_filename=False):
i,url = inp
url = url.split("?")[0]
Expand All @@ -33,7 +33,7 @@ def _download_image_inner(dest, inp, timeout=4, preserve_filename=False):
try: download_url(url, dest/f"{name}{suffix}", show_progress=False, timeout=timeout)
except Exception as e: f"Couldn't download {url}."

# %% ../nbs/09b_vision.utils.ipynb 10
# %% ../nbs/09b_vision.utils.ipynb 9
def download_images(dest, url_file=None, urls=None, max_pics=1000, n_workers=8, timeout=4, preserve_filename=False):
"Download images listed in text file `url_file` to path `dest`, at most `max_pics`"
if urls is None: urls = url_file.read_text().strip().split("\n")[:max_pics]
Expand All @@ -42,15 +42,15 @@ def download_images(dest, url_file=None, urls=None, max_pics=1000, n_workers=8,
parallel(partial(_download_image_inner, dest, timeout=timeout, preserve_filename=preserve_filename),
list(enumerate(urls)), n_workers=n_workers, threadpool=True)

# %% ../nbs/09b_vision.utils.ipynb 12
# %% ../nbs/09b_vision.utils.ipynb 11
def resize_to(img, targ_sz, use_min=False):
"Size to resize to, to hit `targ_sz` at same aspect ratio, in PIL coords (i.e w*h)"
w,h = img.size
min_sz = (min if use_min else max)(w,h)
ratio = targ_sz/min_sz
return int(w*ratio),int(h*ratio)

# %% ../nbs/09b_vision.utils.ipynb 14
# %% ../nbs/09b_vision.utils.ipynb 13
def verify_image(fn):
"Confirm that `fn` can be opened"
try:
Expand All @@ -60,12 +60,12 @@ def verify_image(fn):
return True
except: return False

# %% ../nbs/09b_vision.utils.ipynb 15
# %% ../nbs/09b_vision.utils.ipynb 14
def verify_images(fns):
"Find images in `fns` that can't be opened"
return L(fns[i] for i,o in enumerate(parallel(verify_image, fns)) if not o)

# %% ../nbs/09b_vision.utils.ipynb 16
# %% ../nbs/09b_vision.utils.ipynb 15
def resize_image(file, dest, src='.', max_size=None, n_channels=3, ext=None,
img_format=None, resample=BILINEAR, resume=False, **kwargs ):
"Resize file to dest to max_size"
Expand All @@ -89,7 +89,7 @@ def resize_image(file, dest, src='.', max_size=None, n_channels=3, ext=None,
img.save(dest_fname, img_format, **kwargs)
elif file != dest_fname : shutil.copy2(file, dest_fname)

# %% ../nbs/09b_vision.utils.ipynb 19
# %% ../nbs/09b_vision.utils.ipynb 18
def resize_images(path, max_workers=defaults.cpus, max_size=None, recurse=False,
dest=Path('.'), n_channels=3, ext=None, img_format=None, resample=BILINEAR,
resume=None, **kwargs):
Expand Down
10 changes: 8 additions & 2 deletions nbs/07_vision.core.ipynb
Expand Up @@ -33,6 +33,7 @@
"from fastai.data.all import *\n",
"\n",
"from PIL import Image\n",
"\n",
"try: BILINEAR,NEAREST = Image.Resampling.BILINEAR,Image.Resampling.NEAREST\n",
"except AttributeError: from PIL.Image import BILINEAR,NEAREST"
]
Expand Down Expand Up @@ -82,15 +83,18 @@
"metadata": {},
"outputs": [],
"source": [
"#It didn't use to be necessary to add ToTensor in all but we don't have the encodes methods defined here otherwise.\n",
"#TODO: investigate"
"#|exporti\n",
"@patch\n",
"def __repr__(x:Image.Image):\n",
" return \"<%s.%s image mode=%s size=%dx%d at 0x%X>\" % (x.__class__.__module__, x.__class__.__name__, x.mode, x.size[0], x.size[1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Core vision\n",
"\n",
"> Basic image opening/processing functionality"
]
},
Expand Down Expand Up @@ -1079,6 +1083,8 @@
"ToTensor:\n",
"encodes: (PILMask,object) -> encodes\n",
"(PILBase,object) -> encodes\n",
"(PILMask,object) -> encodes\n",
"(PILBase,object) -> encodes\n",
"decodes: \n",
"<class '__main__.PILImageBW'>\n",
"<class 'fastai.torch_core.TensorImageBW'>\n"
Expand Down

0 comments on commit 0454367

Please sign in to comment.