| """ |
| Utility functions for training |
| """ |
|
|
|
|
| import numpy as np |
| import pandas as pd |
| from scipy.stats import spearmanr, pearsonr |
| from scipy.ndimage import convolve1d |
| from scipy.ndimage import gaussian_filter1d |
| from sklearn import metrics |
|
|
|
|
|
|
|
|
| def label_distribution_smoothing(y, bins=None, ks=5, sigma=2, normalize=True): |
| """ |
| Return a smoothed label distribution derived by convolving a symetric kernel |
| to the empirical label distribution. If bins is None, split the data (y) into bins |
| such that each bin corresponds to 1.0 pH unit. Otherwise if bins is an integer, split |
| bins into as many bins as is specified. |
| See the paper, |
| Yang, Zha, Chen, et al, 2021. Delving into deep imbalanced regression. |
| Code adapted from https://github.com/YyzHarry/imbalanced-regression |
| """ |
| |
| |
| if bins==None: |
| bins = int(np.ceil(np.max(y) - np.min(y))) |
| bin_freqs, bin_borders = np.histogram(y, range=(min(y), max(y)), bins=bins) |
| y_binned = np.zeros(len(y)) |
| for i in range(bins): |
| low, high = bin_borders[i], bin_borders[i+1] |
| locs = np.logical_and((y >= low), (y<= high)) |
| y_binned[locs] = i |
|
|
| |
| half_ks = (ks - 1) // 2 |
| base_kernel = [0.] * half_ks + [1.] + [0.] * half_ks |
| kernel_window = gaussian_filter1d(base_kernel, sigma=sigma) |
| kernel_window /= max(gaussian_filter1d(base_kernel, sigma=sigma)) |
|
|
| |
| bin_kde = convolve1d(np.array(bin_freqs), weights=kernel_window, mode='constant') |
| y_kde = np.array([bin_kde[int(item)] for item in y_binned]) |
|
|
| |
| if normalize: |
| y_kde = y_kde / (np.min(y_kde)) |
| |
| return y_kde |
|
|
|
|
|
|
|
|
| def get_sample_weights(ydata, method='bin_inv', bin_borders=[5,9]): |
| """ |
| Return an array of sample weights computed with different methods. |
| Methods: |
| 'None': No reweighting, weights of ones for all samples |
| 'bin_inv': Inverse of frequency of three main bins [y<=5, 5<y<9, y>=9] |
| 'bin_inv_sqrt': Square root of bin_inverse |
| 'LDS_inv': Inverse of Gaussian kernel density estimate |
| (label distribution smoothing) of target data derived with |
| kernel size of 5, standard deviation of 2, and 100 equidistant bins. |
| 'LDS_inv_sqrt': Square root of LDS_inv |
| 'LDS_extreme': LDS weights are rescaled so that rare values (y<=5, y>=9) are twice |
| more likely to be selected than normal values (5<y<9). |
| """ |
| |
| assert method in ['None', 'bin_inv', 'bin_inv_sqrt', 'LDS_inv', 'LDS_inv_sqrt', |
| 'LDS_extreme'] |
| |
| if method == 'None': |
| |
| weights = np.ones(len(ydata)) |
|
|
| elif method in ['bin_inv', 'bin_inv_sqrt']: |
| |
| y_binned = np.digitize(ydata, bin_borders) |
| bin_class, bin_freqs = np.unique(y_binned, return_counts=True) |
| inv_freq_dict = dict(zip(bin_class, 1 / bin_freqs)) |
| weights = np.array([inv_freq_dict[value] for value in y_binned]) |
|
|
| elif method in ['LDS_inv', 'LDS_inv_sqrt', 'LDS_extreme']: |
| |
| effdist = label_distribution_smoothing(ydata, bins=100) |
| weights = 1 / effdist |
| if method == 'LDS_extreme': |
| |
| relevance = np.logical_or(ydata<=5, ydata>=9).astype(int) |
| relevance = relevance * (1 - 0.5) + 0.5 |
| weights = weights * relevance |
| |
| if method in ['bin_inv_sqrt', 'LDS_inv_sqrt']: |
| weights = np.sqrt(weights) |
| |
| |
| weights = weights / np.mean(weights) |
| |
| return weights |
|
|
|
|
|
|
|
|
| def sample_hyperparameters(param_space, n=200): |
| """ |
| Return hyperparmeters randomly sampled from a dictionary, where keys are hyperparameter names |
| and values are arrays of possible hyperparameters. |
| """ |
| |
| params_list = [] |
| maxsize = int(np.product([len(item) for item in param_space.values()])) |
| |
| while len(params_list) < min([n, maxsize]): |
| sampled_params = {} |
| for key,values in param_space.items(): |
| sampled_params[key] = np.random.choice(values) |
| if sampled_params not in params_list: |
| params_list.append(sampled_params) |
| |
| return params_list |
|
|
|
|
|
|
|
|
| def performance(ytrue, ypred, weights, bins=[5,9]): |
| '''Return a dictionary of performance metrics evaluated on predictions''' |
| |
| perf = {} |
| ytrue, ypred, weights = np.asarray(ytrue), np.asarray(ypred), np.asarray(weights) |
|
|
| |
| p = weights / np.sum(weights) |
| usize = len(weights) |
| rho, r = 0, 0 |
| iters = 100 |
| for _ in range(iters): |
| locs = np.random.choice(range(len(ytrue)), size=usize, p=p, replace=True) |
| rho += float(spearmanr(ytrue[locs], ypred[locs])[0]) |
| r += float(pearsonr(ytrue[locs], ypred[locs])[0]) |
| perf['rho'] = rho / iters |
| perf['r'] = r / iters |
|
|
| |
| perf['rmse'] = float(metrics.mean_squared_error(ytrue, ypred, sample_weight=weights, squared=False)) |
| perf['r2'] = float(metrics.r2_score(ytrue, ypred, sample_weight=weights)) |
| |
| |
| ytrue_binned = np.digitize(ytrue, bins) |
| ypred_binned = np.digitize(ypred, bins) |
| perf['mcc'] = float(metrics.matthews_corrcoef(ytrue_binned, ypred_binned, sample_weight=weights)) |
| f1score, auc = [], [], |
| for val in set(ytrue_binned): |
| ytrue_sel = (ytrue_binned==val).astype(int) |
| ypred_sel = (ypred_binned==val).astype(int) |
| f1score.append(float(metrics.f1_score(ytrue_sel, ypred_sel, sample_weight=weights))) |
| auc.append(float(metrics.roc_auc_score(ytrue_sel, ypred_sel, sample_weight=weights))) |
| perf['f1score_per_bin'] = f1score |
| perf['f1score'] = np.mean(f1score) |
| perf['auc_per_bin'] = auc |
| perf['auc'] = np.mean(auc) |
| |
| return perf |
|
|
|
|