├── python ├── requirements.txt ├── Makefile ├── README.md ├── demo.py ├── opt_ctypes.py ├── opt.cpp └── precise.py ├── figures └── demo.png ├── matlab ├── newton_1d_bnd.m ├── demo.m ├── precise_r70.m ├── precise_a_co96.m └── precise_co96.m └── README.md /python/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | matplotlib 3 | -------------------------------------------------------------------------------- /figures/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bremen79/precise/HEAD/figures/demo.png -------------------------------------------------------------------------------- /python/Makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ 2 | CXX_OPTIONS = -std=c++0x -O3 -msse -msse2 -ffast-math -DNDEBUG 3 | INCLUDE = -I. 4 | 5 | opt.o: opt.cpp 6 | $(CXX) $(INCLUDE) $(CXX_OPTIONS) -o opt.o -c -fpic opt.cpp 7 | 8 | opt_ctypes: opt.o 9 | $(CXX) $(INCLUDE) $(CXX_OPTIONS) opt.o -o lib_opt.so --shared 10 | 11 | all: opt.o opt_ctypes 12 | 13 | clean: 14 | rm *.o; rm *.so; rm *.pyc 15 | -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | ## Setup 2 | 3 | To install all the requirements run: 4 | 5 | ``` 6 | pip3 install -r requirements.txt 7 | make all 8 | ``` 9 | 10 | For macOS: If you are getting ‘cmath not found’ error from compiling, then you should consider downgrading xcode. There was a case with macos Sequoia 15.1.1 where xcode=16.1 led to ‘cmath not found’ and downgrading it to 16.0 fixed it. 11 | 12 | ## Acknowledgments 13 | We thank Ilja Kuzborskij for helping porting the Matlab code to Python and Nicolò Campolongo for suggesting improvements. 14 | -------------------------------------------------------------------------------- /matlab/newton_1d_bnd.m: -------------------------------------------------------------------------------- 1 | function [x,fval] = newton_1d_bnd(funfcn,df,df2,ax,bx) 2 | %NEWTON_1D_BND Newton algorithm helper function. 3 | 4 | % This algorithm is described in Orabona and Jun, "Tight Concentrations 5 | % and Confidence Sequences from the Regret of Universal Portfolio", ArXiv 6 | % 2021. 7 | 8 | deriv=inf; 9 | x=0; 10 | x_old=inf; 11 | while abs(deriv)>0.001 && abs(x-x_old)>0.001 12 | x_old=x; 13 | deriv=df(x); 14 | if x==ax && deriv<0 15 | break 16 | elseif x==bx && deriv>0 17 | break 18 | end 19 | if abs(deriv)>1e3 20 | update=0.01*sign(deriv); 21 | else 22 | deriv2=df2(x); 23 | update=-deriv/deriv2; 24 | end 25 | x=x+update; 26 | x=max(min(x,bx),ax); 27 | end 28 | fval=funfcn(x); 29 | 30 | -------------------------------------------------------------------------------- /matlab/demo.m: -------------------------------------------------------------------------------- 1 | % demo.m 2 | 3 | close all 4 | clear 5 | 6 | n=10000; 7 | delta=0.05; 8 | 9 | % i.i.d. uniform samples in [0,1] 10 | x=rand(n,1); 11 | 12 | [l_co96,u_co96] = precise_co96(x,delta); 13 | [l_a_co96,u_a_co96] = precise_a_co96(x,delta); 14 | [l_r70,u_r70] = precise_r70(x,delta); 15 | 16 | figure 17 | h1=semilogx(l_co96,'r','LineWidth',2.0); hold on; semilogx(u_co96,'r','LineWidth',2.0); 18 | h2=semilogx(l_a_co96,'g','LineWidth',2.0); hold on; semilogx(u_a_co96,'g','LineWidth',2.0); 19 | h3=semilogx(l_r70,'b','LineWidth',2.0); hold on; semilogx(u_r70,'b','LineWidth',2.0); 20 | grid on 21 | legend([h1,h2,h3],'PRECiSE-CO96', 'PRECiSE-A-CO96', 'PRECiSE-R70') 22 | xlabel('Number of samples (log scale)') 23 | ylabel('Confidence Sequences') 24 | title('Confidence sequences for a uniform r.v. in [0,1], \delta=0.05') 25 | -------------------------------------------------------------------------------- /python/demo.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | from precise import precise_co96 5 | 6 | 7 | n=10000; 8 | delta=0.05; 9 | 10 | # i.i.d. uniform samples in [0,1] 11 | vector = np.random.uniform(size=n) 12 | print(vector) 13 | 14 | lcblist, ucblist = precise_co96(vector, delta) 15 | print("Upper and lower bounds") 16 | print(lcblist, ucblist) 17 | 18 | plt.plot(np.arange(1, n + 1), lcblist, label='PRECiSE-CO96', color='red') 19 | plt.plot(np.arange(1, n + 1), ucblist, color='red') 20 | plt.ylim(0, 1) 21 | plt.xlim(1, n) 22 | plt.xscale('log') 23 | plt.xlabel('Number of samples (log scale)') 24 | plt.ylabel('Confidence Sequences') 25 | plt.title(r'Confidence sequences for a uniform r.v. in [0,1], $\delta=0.05$') 26 | plt.legend() 27 | plt.grid(True, which="both", linestyle="--", linewidth=0.5) 28 | plt.show() 29 | -------------------------------------------------------------------------------- /python/opt_ctypes.py: -------------------------------------------------------------------------------- 1 | from ctypes import c_uint, c_int, c_uint32, c_uint64, c_float, c_double, c_char_p, c_void_p, CFUNCTYPE, c_bool, Structure, POINTER, byref 2 | import numpy as np 3 | import numpy.ctypeslib as npct 4 | 5 | arraylf = npct.ndpointer(dtype=np.float32, ndim=1, flags='C_CONTIGUOUS') 6 | array2f = npct.ndpointer(dtype=np.float32, ndim=2, flags='C_CONTIGUOUS') 7 | 8 | lib = npct.load_library("lib_opt.so", ".") 9 | 10 | lib.find_mean_max_log_wealth_constrained.restype = c_float 11 | lib.find_mean_max_log_wealth_constrained.argtypes= [arraylf, c_uint32, c_float, c_float, POINTER(c_float)] 12 | 13 | 14 | def find_mean_max_log_wealth_constrained( 15 | X: np.ndarray, 16 | bmin: float, 17 | bmax: float, 18 | m_try: float 19 | ): 20 | x_out = c_float() 21 | 22 | X_ = X - m_try 23 | log_wealth = lib.find_mean_max_log_wealth_constrained( 24 | X_.T.astype(np.float32, order='C'), 25 | c_uint32(X.size), 26 | c_float(bmin), 27 | c_float(bmax), 28 | byref(x_out), 29 | ) 30 | return (log_wealth, x_out) 31 | -------------------------------------------------------------------------------- /python/opt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | template int sgn(T val) { 10 | return (T(0) < val) - (val < T(0)); 11 | } 12 | 13 | float objective(const float *g, 14 | const uint32_t length, 15 | const float bet) { 16 | float total = 0; 17 | 18 | for (int i=0; i < length; ++i) { 19 | total += log(1.0 + g[i]*bet); 20 | } 21 | 22 | return total; 23 | } 24 | 25 | float d_objective(const float *g, 26 | const uint32_t length, 27 | const float bet) { 28 | float total = 0; 29 | for (int i=0; i < length; ++i) { 30 | total += (g[i] / (1.0 + g[i]*bet)); 31 | } 32 | return total; 33 | } 34 | 35 | float d2_objective(const float *g, 36 | const uint32_t length, 37 | const float bet) { 38 | float total = 0; 39 | for (int i=0; i < length; ++i) { 40 | total += - pow(g[i] / (1.0 + g[i]*bet), 2); 41 | } 42 | return total; 43 | } 44 | 45 | 46 | float newton_1d_bnd(const float *g, 47 | const uint32_t length, 48 | const float ax, 49 | const float bx, 50 | const float x_init, 51 | float &x_out) { 52 | float deriv = numeric_limits::max(); 53 | float x_old = numeric_limits::max(); 54 | float x = x_init; 55 | 56 | while ( (abs(deriv) > 0.001) && (abs(x-x_old) > 0.01) ) { 57 | x_old = x; 58 | deriv = d_objective(g, length, x); 59 | if ((x == ax) && (deriv < 0)) { 60 | break; 61 | } 62 | 63 | if ((x == bx) & (deriv > 0)) { 64 | break; 65 | } 66 | 67 | float update = 0; 68 | if (abs(deriv) > 1e3) { 69 | update = 0.01 * sgn(deriv); 70 | } 71 | else { 72 | const float deriv2 = d2_objective(g, length, x); 73 | update = -deriv/deriv2; 74 | } 75 | 76 | x += update; 77 | x = max(min(x, bx), ax); 78 | } 79 | 80 | const float fval = objective(g, length, x); 81 | x_out = x; 82 | 83 | return fval; 84 | } 85 | 86 | extern "C" float find_mean_max_log_wealth_constrained(const float *X, 87 | const uint32_t n, 88 | const float bmin, 89 | const float bmax, 90 | float &x_out) { 91 | 92 | float x_init = 0; 93 | float sum_log_W_star = 0; 94 | 95 | float update = 0; 96 | 97 | sum_log_W_star = newton_1d_bnd(X, n, bmin, bmax, x_init, x_out); 98 | return sum_log_W_star; 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tight Concentrations and Confidence Sequences from the Regret of Universal Portfolio 2 | 3 | ## Overview 4 | 5 | This repository contains the software implementation of the methods presented in [Tight Concentrations and Confidence Sequences from the Regret of Universal Portfolio](https://ieeexplore.ieee.org/document/10315047). We introduce novel methods for deriving time-uniform concentration inequalities and confidence sequences using portfolio algorithms. These results have direct applications in statistics and machine learning, specifically for constructing valid and tight confidence intervals. 6 | 7 | ## Problem Statement 8 | 9 | Estimating the mean of bounded random variables from samples is a fundamental task in statistics. Confidence sequences---confidence intervals that hold uniformly over time---are particularly valuable for adaptive decision making. However, traditional methods often produce vacuous intervals for small sample sizes, limiting their practical usability. 10 | 11 | ## Our Approach 12 | 13 | Building upon the theory of online betting and portfolio selection, we propose algorithms that leverage the regret of universal portfolio algorithms to generate tighter and more practical confidence sequences. In particular, the obtained confidence sequences are state-of-the-art and never vacuous, even with a single sample. 14 | 15 | ## Features 16 | 17 | - **Time-uniform Confidence Sequences**: Generate confidence intervals that hold across all time steps. 18 | - **Never-vacuous Guarantees**: Ensures practical usability even for small sample sizes. 19 | - **Optimal Asymptotics**: Matches the best possible performance for large datasets. 20 | - **Efficient Algorithms**: Includes both exact and approximated methods for fast computations. 21 | 22 | ![demo](figures/demo.png) 23 | 24 | ## Algorithms 25 | 26 | - PRECiSE-CO96: A method leveraging regret guarantees of universal portfolio algorithms for optimal confidence sequences. 27 | - PRECiSE-A-CO96: A computationally efficient variant with constant-time updates per sample. 28 | - PRECiSE-R70: A portfolio algorithm inspired by Robbins' mixture method, achieving state-of-the-art performance while satisfying the law of iterated logarithm. 29 | 30 | The random variable for all algorithms must be in [0,1], they can be continuous or discrete. 31 | 32 | The matlab folder contains the implementation of all the algorithms in Matlab, while the Python folder contains the implementation of PRECiSE-CO96 only. 33 | Take a look at the demo scripts in both directories for an example of usage. 34 | -------------------------------------------------------------------------------- /matlab/precise_r70.m: -------------------------------------------------------------------------------- 1 | function [lcblist,ucblist] = precise_r70(x,delta) 2 | %PRECISE_R70 Portfolio REgret for Confidence SEquences using 3 | % Robbins [1970]. 4 | % [L,U] = PRECISE_R70(X,delta) produces two matrices, of the same 5 | % dimension as X and with lower and upper confidence sequences with 6 | % probability of error delta. X is numer-of-samples by 7 | % number-of-repetitions, where each sample must be in [0,1]. 8 | 9 | % This algorithm is described in Orabona and Jun, "Tight Concentrations 10 | % and Confidence Sequences from the Regret of Universal Portfolio", ArXiv 11 | % 2021. 12 | 13 | [T,repetitions]=size(x); 14 | 15 | lcblist = zeros(T,repetitions); 16 | ucblist = zeros(T,repetitions); 17 | 18 | loginvdelta=log(1/delta); 19 | mn=inf; 20 | mx=-inf; 21 | 22 | for j=1:repetitions 23 | 24 | tic 25 | fprintf('Repetition %d\n',j); 26 | 27 | c = x(:,j); 28 | 29 | m_lb_old=eps; 30 | m_ub_old=1-eps; 31 | 32 | for i=1:length(c) 33 | mean_c=mean(c(1:i)); 34 | 35 | % upper confidence interval 36 | m_ub = m_ub_old; 37 | m_lb = max(m_lb_old,mean_c); 38 | 39 | mn=min(c(i),mn); 40 | mx=max(c(i),mx); 41 | 42 | % calculate regret 43 | m_try = m_ub ; 44 | [log_W_star] = find_max_log_wealth_constrained_lil(c(1:i)-m_try,mn-m_try,mx-m_try); 45 | if log_W_star >= loginvdelta 46 | while (m_ub - m_lb)>0.0001 47 | m_try = (m_ub + m_lb)/2; 48 | [log_W_star] = find_max_log_wealth_constrained_lil(c(1:i)-m_try,mn-m_try,mx-m_try); 49 | if log_W_star >= loginvdelta 50 | m_ub = m_try; 51 | else 52 | m_lb = m_try; 53 | end 54 | end 55 | end 56 | ucblist(i,j) = m_ub; 57 | m_ub_old=m_ub; 58 | 59 | 60 | % lower confidence interval 61 | m_ub = min(m_ub,mean_c); 62 | m_lb = m_lb_old; 63 | 64 | % calculate regret 65 | m_try = m_lb ; 66 | [log_W_star] = find_max_log_wealth_constrained_lil(c(1:i)-m_try,mn-m_try,mx-m_try); 67 | if log_W_star >= loginvdelta 68 | while (m_ub - m_lb)>0.0001 69 | m_try = (m_ub + m_lb)/2; 70 | [log_W_star] = find_max_log_wealth_constrained_lil(c(1:i)-m_try,mn-m_try,mx-m_try); 71 | if log_W_star >= loginvdelta 72 | m_lb = m_try; 73 | else 74 | m_ub = m_try; 75 | end 76 | end 77 | end 78 | lcblist(i,j) = m_lb; 79 | m_lb_old=m_lb; 80 | end 81 | toc 82 | end 83 | 84 | function [fval] = find_max_log_wealth_constrained_lil(g,mn,mx) 85 | myf = @(bet) prod(1 + g.*bet); 86 | df = @(bet) sum(g./(1 + g.*bet)); 87 | df2 = @(bet) -sum((g./(1 + g.*bet)).^2); 88 | 89 | [betstar, fval] = newton_1d_bnd(myf, df, df2, -1, 1); 90 | 91 | pdf = @(bet) log(log(6.6)+1)/(2*abs(bet)*(1+log(6.6/abs(bet)))*(log(1+log(6.6/abs(bet))))^2); 92 | 93 | V=sum(g.^2); 94 | 95 | if betstar>0 96 | s=mn; 97 | else 98 | s=mx; 99 | end 100 | 101 | absbetstar=abs(betstar); 102 | if absbetstar~=1 103 | delta=(1+min(s*betstar,0))/sqrt(V); 104 | else 105 | delta=0; 106 | end 107 | delta=absbetstar-max(absbetstar-delta,0); 108 | fval=log(max((fval-1)/(eps+log(fval))*abs(betstar),fval*exp(-1/2*delta^2/(1+min(s*betstar,0))^2*V)*delta)*pdf(absbetstar+eps)); 109 | -------------------------------------------------------------------------------- /python/precise.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | from math import lgamma as gammaln, exp, sqrt, pi, asin, pi, log as ln 3 | from math import ceil, floor 4 | 5 | import numpy as np 6 | 7 | from opt_ctypes import find_mean_max_log_wealth_constrained 8 | 9 | def regret_func(b,k,t): 10 | eps = np.finfo(float).eps 11 | return k*ln(b+eps)+(t-k)*ln(1-b+eps)+gammaln(t+1)+2*gammaln(1/2)-gammaln(k+1/2)-gammaln(t-k+1/2) 12 | 13 | def precise_co96( 14 | x: np.ndarray, 15 | delta: float, 16 | ) -> (np.array, np.array): 17 | """ PRECiSE-CO96 18 | 19 | Args: 20 | x: 1-D array of data points in [0,1] 21 | delta: error probability 22 | 23 | Returns: 24 | Tuple with two arrays (corresponding to LCB and UCB measurements) of length # data points. 25 | """ 26 | 27 | prec = np.finfo(float).eps 28 | 29 | n = x.size 30 | lcblist = np.zeros(n) 31 | ucblist = np.zeros(n) 32 | 33 | m_lb_old=prec 34 | m_ub_old=1-prec 35 | 36 | for i in range(1, n+1): 37 | loginvdelta = ln(1/delta) 38 | 39 | mean_c = x[:i].mean() 40 | 41 | # upper confidence interval 42 | m_ub = m_ub_old 43 | m_lb = max(m_lb_old, mean_c) 44 | 45 | # calculate regret 46 | m_try = m_ub 47 | bmax = 1 / m_try 48 | bmin = -1 / (1-m_try) 49 | 50 | log_W_star, bet_star = find_mean_max_log_wealth_constrained( 51 | x[:i], 52 | bmin, 53 | bmax, 54 | m_try 55 | ) 56 | b=(-bmin+bet_star)/(bmax-bmin); 57 | bound=max(regret_func(ceil(b*i-0.5)/i,ceil(b*i-0.5),i),regret_func(floor(mean_c*i+0.5)/i,floor(mean_c*i+0.5),i)) 58 | #bound = ln(sqrt(pi)*gamma(i+1)/gamma(i+0.5)) # non-instance-dependent bound 59 | 60 | if log_W_star - bound >= loginvdelta: 61 | while (m_ub - m_lb) > 0.0001: 62 | m_try = (m_ub + m_lb)/2 63 | bmax = 1 / m_try 64 | bmin = -1 / (1-m_try) 65 | 66 | log_W_star, bet_star = find_mean_max_log_wealth_constrained( 67 | x[:i], 68 | bmin, 69 | bmax, 70 | m_try 71 | ) 72 | if log_W_star - bound >= loginvdelta: 73 | m_ub = m_try 74 | b=(-bmin+bet_star)/(bmax-bmin) 75 | bound=max(regret_func(ceil(b*i-0.5)/i,ceil(b*i-0.5),i),regret_func(floor(mean_c*i+0.5)/i,floor(mean_c*i+0.5),i)) 76 | else: 77 | m_lb = m_try 78 | 79 | ucblist[i-1] = m_ub 80 | m_ub_old = m_ub 81 | 82 | # lower confidence interval 83 | m_ub = min(m_ub, mean_c) 84 | m_lb = m_lb_old 85 | 86 | # calculate regret 87 | m_try = m_lb 88 | bmax = 1 / m_try 89 | bmin = -1 / (1-m_try) 90 | log_W_star, bet_star = find_mean_max_log_wealth_constrained( 91 | x[:i], 92 | bmin, 93 | bmax, 94 | m_try 95 | ) 96 | b=min((-bmin+bet_star)/(bmax-bmin),1) 97 | bound=max(regret_func(floor(b*i+0.5)/i,floor(b*i+0.5),i),regret_func(ceil(mean_c*i-0.5)/i,ceil(mean_c*i-0.5),i)) 98 | 99 | if log_W_star - bound >= loginvdelta: 100 | while (m_ub - m_lb) > 0.0001: 101 | m_try = (m_ub + m_lb)/2 102 | bmax = 1 / m_try 103 | bmin = -1 / (1-m_try) 104 | 105 | log_W_star, bet_star = find_mean_max_log_wealth_constrained( 106 | x[:i], 107 | bmin, 108 | bmax, 109 | m_try 110 | ) 111 | if log_W_star-bound >= loginvdelta: 112 | m_lb = m_try 113 | b=min((-bmin+bet_star)/(bmax-bmin),1) 114 | bound=max(regret_func(floor(b*i+0.5)/i,floor(b*i+0.5),i),regret_func(ceil(mean_c*i-0.5)/i,ceil(mean_c*i-0.5),i)) 115 | else: 116 | m_ub = m_try 117 | 118 | lcblist[i-1] = m_lb 119 | m_lb_old = m_lb 120 | 121 | return (lcblist, ucblist) 122 | 123 | -------------------------------------------------------------------------------- /matlab/precise_a_co96.m: -------------------------------------------------------------------------------- 1 | function [lcblist,ucblist] = precise_a_co96(x,delta) 2 | %PRECISE_A_CO96 Portfolio REgret for Confidence SEquences with 3 | % Approximation using Cover and Ordentlich [1996]. 4 | % [L,U] = PRECISE_A_CO96(X,delta) produces two matrices, of the same 5 | % dimension as X and with lower and upper confidence sequences with 6 | % probability of error delta. X is numer-of-samples by 7 | % number-of-repetitions, where each sample must be in [0,1]. 8 | 9 | % This algorithm is described in Orabona and Jun, "Tight Concentrations 10 | % and Confidence Sequences from the Regret of Universal Portfolio", ArXiv 11 | % 2021. 12 | 13 | N = size(x,1); 14 | n_try = size(x,2); 15 | dt = delta; 16 | tol = 1e-4; 17 | 18 | reg = @(t) log(sqrt(pi)) + gammaln(t+1) - gammaln(t+.5); 19 | 20 | n_algo = 2; 21 | 22 | lcblist = zeros(N,n_try); 23 | ucblist = zeros(N,n_try); 24 | for i_try=1:n_try 25 | rdata = zeros(N,n_algo,2); 26 | runningmax = zeros(1,n_algo); 27 | runningmin = ones(1,n_algo); 28 | 29 | for t=1:N 30 | data = x(1:t,i_try); %Y(1:t); 31 | me = mean(data); 32 | va = var(data, 1); 33 | lcb = zeros(1,n_algo); 34 | ucb = ones(1,n_algo); 35 | i_algo = 0; 36 | 37 | %--- fan 38 | i_algo = i_algo + 1; 39 | rhs = reg(t) + log(1/dt); 40 | lb = 0; 41 | ub = me; 42 | lcbmaxfn = @(m,me,va,t) max(max_logwealth_fan3_lcb(m, me, va, t), max_logwealth_kl(m, me, va, t)); 43 | if (lb == ub || lcbmaxfn(lb,me,va,t) - rhs <= 0) 44 | lcb(i_algo) = 0.0; 45 | else 46 | [lcb(i_algo), uu] = bsearch(@(m) lcbmaxfn(m,me,va,t) - rhs, lb, ub, tol); 47 | end 48 | lb = me; 49 | ub = 1; 50 | ucbmaxfn = @(m,me,va,t) max(max_logwealth_fan3_ucb(m, me, va, t), max_logwealth_kl(m, me, va, t)); 51 | if (lb == ub || ucbmaxfn(ub, me, va, t) - rhs <= 0) 52 | ucb(i_algo) = 1.0; 53 | else 54 | [ll, ucb(i_algo)] = bsearch(@(m) ucbmaxfn(m,me,va,t) - rhs, lb, ub, tol); 55 | end 56 | 57 | runningmax(:) = max(runningmax, lcb); 58 | runningmin(:) = min(runningmin, ucb); 59 | rdata(t,:,1) = runningmax; 60 | rdata(t,:,2) = runningmin; 61 | end 62 | lcblist(:,i_try) = rdata(:,1,1); 63 | ucblist(:,i_try) = rdata(:,1,2); 64 | end 65 | 66 | end 67 | 68 | function max_logwealth = max_logwealth_fan3_lcb(m, mu_hat, var_hat, t) 69 | if (m==0.0) 70 | max_logwealth = (.5*(mu_hat^2)/(var_hat + mu_hat^2))*t; 71 | elseif m == mu_hat 72 | max_logwealth = 0.0; 73 | else 74 | A = ((mu_hat - m)/m); 75 | B = ((var_hat + (mu_hat - m)^2)/m^2); 76 | lam = A/(A+B); 77 | max_logwealth = (A*A/(A+B) - (-log(1-lam) - lam)*B)*t; 78 | end 79 | end 80 | 81 | function max_logwealth = max_logwealth_fan3_ucb(m, mu_hat, var_hat, t) 82 | if (m==1.0) 83 | max_logwealth = (.5*((1 - mu_hat)^2)/(var_hat + (1 - mu_hat)^2))*t ; 84 | elseif m == mu_hat 85 | max_logwealth = 0.0; 86 | else 87 | A = ((m - mu_hat)/(1-m)); 88 | B = ((var_hat + (m - mu_hat)^2)/(1-m)^2); 89 | lam = A/(A+B); 90 | max_logwealth = (A*A/(A+B) - (-log(1-lam) - lam)*B)*t; 91 | end 92 | end 93 | 94 | function val = kl(p,q) 95 | if (p == 0) 96 | val = (1-p)*log((1-p)/(1-q)); 97 | elseif (p == 1) 98 | val = p*log(p/q); 99 | else 100 | val = p*log(p/q) + (1-p)*log((1-p)/(1-q)); 101 | end 102 | end 103 | 104 | function max_logwealth = max_logwealth_kl(m, mu_hat, var_hat, t) 105 | max_logwealth = t*kl(mu_hat,m); 106 | end 107 | 108 | function [lb,ub] = bsearch(fn, lb, ub, tol) 109 | if (~exist('tol','var')) 110 | tol = 1e-6; 111 | end 112 | 113 | fnlb = fn(lb); 114 | fnub = fn(ub); 115 | assert(fnlb * fnub ~= 0.0) 116 | if ~(( fnlb <= 0 && fnub >= 0 ) || ( fnlb >= 0 && fnub <= 0)) 117 | keyboard; 118 | end 119 | assert(( fnlb <= 0 && fnub >= 0 ) || ( fnlb >= 0 && fnub <= 0)) 120 | sign_fnlb = 1.0; 121 | if (fnlb <= 0 && fnub >= 0) 122 | sign_fnlb = -1.0; 123 | end 124 | max_iter = 1000; 125 | i_iter = 0; 126 | while ub-lb >= tol && i_iter <= max_iter 127 | mid = (lb+ub)/2; 128 | val = fn(mid); 129 | if (val*sign_fnlb > 0.0) % when the sign of val is equal to that of fn(lb) 130 | lb = mid; 131 | else 132 | ub = mid; 133 | end 134 | i_iter = i_iter + 1; 135 | end 136 | if (i_iter >= max_iter) 137 | fprintf("WARNING: max_iter has reached\n"); 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /matlab/precise_co96.m: -------------------------------------------------------------------------------- 1 | function [lcblist,ucblist] = precise_co96(x,delta) 2 | %PRECISE_CO96 Portfolio REgret for Confidence SEquences using Cover and 3 | % Ordentlich [1996]. 4 | % [L,U] = PRECISE_CO96(X,delta) produces two matrices, of the same 5 | % dimension as X and with lower and upper confidence sequences with 6 | % probability of error delta. X is numer-of-samples by 7 | % number-of-repetitions, where each sample must be in [0,1]. 8 | 9 | % This algorithm is described in Orabona and Jun, "Tight Concentrations 10 | % and Confidence Sequences from the Regret of Universal Portfolio", ArXiv 11 | % 2021. 12 | 13 | func = @(b,k,t) k*log(b+eps)+(t-k)*log(1-b+eps)+gammaln(t+1)+2*gammaln(1/2)-gammaln(k+1/2)-gammaln(t-k+1/2); 14 | 15 | [T,repetitions]=size(x); 16 | 17 | lcblist = zeros(T,repetitions); 18 | ucblist = zeros(T,repetitions); 19 | 20 | loginvdelta=log(1/delta); 21 | 22 | for j=1:repetitions 23 | 24 | tic 25 | fprintf('Repetition %d\n',j); 26 | 27 | c = x(:,j); 28 | 29 | m_lb_old=eps; 30 | m_ub_old=1-eps; 31 | 32 | 33 | for i=1:length(c) 34 | mean_c=mean(c(1:i)); 35 | 36 | % upper confidence interval 37 | m_ub = m_ub_old; 38 | m_lb = max(m_lb_old,mean_c); 39 | 40 | % calculate regret 41 | m_try = m_ub ; 42 | bmax=1/m_try; 43 | bmin=-1/(1-m_try); 44 | [bet_star, log_W_star] = find_max_log_wealth_constrained(c(1:i)-m_try,bmin,bmax); 45 | b=(-bmin+bet_star)/(bmax-bmin); 46 | bound=max(func(ceil(b*i-0.5)/i,ceil(b*i-0.5),i),func(floor(mean_c*i+0.5)/i,floor(mean_c*i+0.5),i)); 47 | if log_W_star - bound >= loginvdelta 48 | while (m_ub - m_lb)>0.0001 49 | m_try = (m_ub + m_lb)/2; 50 | bmax=1/m_try; 51 | bmin=-1/(1-m_try); 52 | [bet_star, log_W_star] = find_max_log_wealth_constrained(c(1:i)-m_try,bmin,bmax); 53 | if log_W_star-bound >= loginvdelta 54 | m_ub = m_try; 55 | b=(-bmin+bet_star)/(bmax-bmin); 56 | bound=max(func(ceil(b*i-0.5)/i,ceil(b*i-0.5),i),func(floor(mean_c*i+0.5)/i,floor(mean_c*i+0.5),i)); 57 | %if bound2> bound+0.0001 58 | % disp 'err'; 59 | %end 60 | %bound=min(bound2, bound); 61 | else 62 | m_lb = m_try; 63 | end 64 | end 65 | end 66 | ucblist(i,j) = m_ub; 67 | m_ub_old=m_ub; 68 | 69 | 70 | % lower confidence interval 71 | m_ub = min(m_ub,mean_c); 72 | m_lb = m_lb_old; 73 | 74 | % calculate regret 75 | m_try = m_lb; 76 | bmax=1/m_try; 77 | bmin=-1/(1-m_try); 78 | [bet_star, log_W_star] = find_max_log_wealth_constrained(c(1:i)-m_try,bmin,bmax); 79 | b=min((-bmin+bet_star)/(bmax-bmin),1); 80 | bound=max(func(floor(b*i+0.5)/i,floor(b*i+0.5),i),func(ceil(mean_c*i-0.5)/i,ceil(mean_c*i-0.5),i)); 81 | if log_W_star - bound >= loginvdelta 82 | while (m_ub - m_lb)>0.0001 83 | m_try = (m_ub + m_lb)/2; 84 | bmax=1/m_try; 85 | bmin=-1/(1-m_try); 86 | [bet_star, log_W_star] = find_max_log_wealth_constrained(c(1:i)-m_try,bmin,bmax); 87 | if log_W_star-bound >= loginvdelta 88 | m_lb = m_try; 89 | b=min((-bmin+bet_star)/(bmax-bmin),1); 90 | bound=max(func(floor(b*i+0.5)/i,floor(b*i+0.5),i),func(ceil(mean_c*i-0.5)/i,ceil(mean_c*i-0.5),i)); 91 | %if bound2> bound+0.0001 92 | % disp 'err'; 93 | %end 94 | %bound=min(bound2, bound); 95 | else 96 | m_ub = m_try; 97 | end 98 | end 99 | end 100 | lcblist(i,j) = m_lb; 101 | m_lb_old=m_lb; 102 | end 103 | toc 104 | end 105 | 106 | function [betstar, fval] = find_max_log_wealth_constrained(g,bmin,bmax) 107 | % finds maximum wealth of max_{beta in [bmin,bmax]} prod_{i=1}^t (1+beta * g(i)) 108 | 109 | myf = @(bet) log(prod(1 + g.*bet)); 110 | df = @(bet) sum(g./(1 + g.*bet)); 111 | df2 = @(bet) -sum((g./(1 + g.*bet)).^2); 112 | 113 | [betstar, fval] = newton_1d_bnd(myf, df, df2, max(bmin,-1e10), min(bmax,1e10)); 114 | --------------------------------------------------------------------------------