Fixing inconsistencies. Model up to ~94% on MNIST. Subtle further tuning likely required.

This commit is contained in:
Austin Garrett
2018-04-06 16:28:08 -04:00
parent ee95571d71
commit 3ad29a4f71
3 changed files with 240 additions and 87 deletions
+150 -41
View File
@@ -1,4 +1,7 @@
import os
import math import math
import random
import data_utils import data_utils
import time import time
@@ -7,10 +10,17 @@ import torch
from torch import nn from torch import nn
from torch.autograd import Variable from torch.autograd import Variable
from torch.utils.data import Dataset, DataLoader from torch.utils.data import Dataset, DataLoader
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from pointcnn.core import rPointCNN from pointcnn.core import rPointCNN
from pointcnn.util import knn_indices_func_gpu from pointcnn.util import knn_indices_func_gpu
from pointcnn.layers import Dense from pointcnn.layers import Dense
from visualize import *
random.seed(0)
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
x = 2 x = 2
@@ -44,29 +54,57 @@ class Classifier(nn.Module):
def __init__(self): def __init__(self):
super(Classifier, self).__init__() super(Classifier, self).__init__()
self.pcnn = nn.Sequential( self.pcnn1 = paPointCNN( 1, 32, 8, 1, -1)
paPointCNN( 1, 32, 8, 1, 256), self.pcnn2 = nn.Sequential(
paPointCNN( 32, 64, 8, 2, 256), paPointCNN( 32, 64, 8, 2, -1),
paPointCNN( 64, 96, 8, 4, 256), paPointCNN( 64, 96, 8, 4, -1),
paPointCNN( 96, 128, 12, 4, 120), paPointCNN( 96, 128, 12, 4, 120),
paPointCNN(128, 160, 12, 6, 120), paPointCNN(128, 160, 12, 6, 120)
) )
self.fcn = nn.Sequential( self.fcn = nn.Sequential(
Dense(160, 128), Dense(160, 128),
Dense(128, 64, drop_rate = 0.5), Dense(128, 64, drop_rate = 0.5),
Dense(64, 10, activation = None) Dense( 64, 10, with_bn = False, activation = None)
) )
self.log_softmax = nn.LogSoftmax()
def forward(self, x): def forward(self, x):
x = self.pcnn(x)[1] # grab features x = self.pcnn1(x)
if False:
print("Making graph...")
k = make_dot(x[1])
print("Viewing...")
k.view()
print("DONE")
assert False
x = self.pcnn2(x)[1] # grab features
logits = self.fcn(x) logits = self.fcn(x)
# logits = torch.mean(logits, dim = 1) logits_mean = torch.mean(logits, dim = 1)
return logits return logits_mean
# log_probs = self.log_softmax(logits)
# return log_probs """
def get_indices(batch_size, sample_num, point_num, random_sample = True):
indices = []
for i in range(batch_size):
if random_sample:
# point_num >= sample_num generally
choices = np.random.choice(point_num, sample_num, replace = (point_num < sample_num))
else:
# This modulo generally not used.
choices = np.arange(sample_num) % point_num
choices = np.expand_dims(choices, axis = 0)
b_idx_mat = np.full_like(choices, i)
# Each set of choices is paired with its batch index.
choices_2d = np.concatenate((b_idx_max, choices), axis = 0)
indices.append(choices_2d)
return np.stack(indices, axis = 1) # (2, batch_size,
"""
model = Classifier().cuda() model = Classifier().cuda()
@@ -95,58 +133,129 @@ batch_num = batch_num_per_epoch * num_epochs
training_set = mnist_dataset(data_train, label_train) training_set = mnist_dataset(data_train, label_train)
training_loader = DataLoader(training_set, batch_size = batch_size) training_loader = DataLoader(training_set, batch_size = batch_size)
testing_batch_size = 256
testing_set = mnist_dataset(data_val, label_val) testing_set = mnist_dataset(data_val, label_val)
testing_loader = DataLoader(testing_set, batch_size = 1) testing_loader = DataLoader(testing_set, batch_size = testing_batch_size)
lr = 0.01
decay_steps = 8000
decay_rate = 0.6
lr_min = 0.00001
optimizer = torch.optim.SGD(model.parameters(), lr = 0.01, momentum = 0.9) optimizer = torch.optim.SGD(model.parameters(), lr = 0.01, momentum = 0.9)
loss_fn = nn.NLLLoss() loss_fn = nn.CrossEntropyLoss()
for _ in range(num_epochs): global_step = 1
n = 0 model_save_dir = os.path.join(CURRENT_DIR, "models", "mnist2")
os.makedirs(model_save_dir, exist_ok = True)
losses = []
accuracies = []
if False:
latest_model = sorted(os.listdir(model_save_dir))[-1]
model.load_state_dict(torch.load(os.path.join(model_save_dir, latest_model)))
for e in range(1, num_epochs + 1):
print("EPOCH %i of %i" % (e, num_epochs))
m_loc = os.path.join(model_save_dir, "save_e%.4d" % e)
torch.save(model.state_dict(), m_loc)
np.savez_compressed(os.path.join(CURRENT_DIR, "losses"), losses)
np.savez_compressed(os.path.join(CURRENT_DIR, "accuracies"), accuracies)
# Decaying learning rate
if e > 1:
lr *= decay_rate ** (global_step // decay_steps)
if lr > lr_min:
print("NEW LEARNING RATE:", lr)
optimizer = torch.optim.SGD(model.parameters(), lr = lr, momentum = 0.9)
for data, label in training_loader: for data, label in training_loader:
n += 1 model = model.train()
data = Variable(data).cuda() label = label.long()
label = Variable(label.long()).cuda()
P = data[:,:,:3] P = data[:,:,:3]
F = data[:,:,3:] F = data[:,:,3:]
offset = int(random.gauss(0, sample_num // 8))
offset = max(offset, -sample_num // 4)
offset = min(offset, sample_num // 4)
sample_num_train = sample_num + offset
# indices = get_indices(batch_size, sample_num_train, point_num)
indices = np.random.choice(P.size()[1], sample_num_train, replace = False).tolist()
P_sampled = P[:,indices,:]
F_sampled = F[:,indices,:]
P_sampled = Variable(P_sampled).cuda()
F_sampled = Variable(F_sampled).cuda()
if False:
P_draw = P_sampled.data.cpu().numpy()
# fig = plt.figure()
# ax = fig.gca(projection = '3d')
# ax.scatter(P_draw[25,:,0], P_draw[25,:,1], P_draw[25,:,2], c = 'k')
# plt.show()
plt.style.use('grayscale')
plt.axis([-3, 3, -3, 3])
plt.scatter(P_draw[25,:,0], -P_draw[25,:,1], c = 1 - F_sampled[25,:,0], marker = ',', s = 25)
print("LABEL:", label[25])
plt.show()
optimizer.zero_grad() optimizer.zero_grad()
t0 = time.time() t0 = time.time()
out = model((P, F)) out = model((P_sampled, F_sampled))
print(out) loss = loss_fn(out, Variable(label.long()).cuda())
loss = loss_fn(out, label)
loss.backward() loss.backward()
optimizer.step() optimizer.step()
print("loss:", loss.data[0]) if global_step % 25 == 0:
loss_v = loss.data[0]
print("Loss:", loss_v)
else:
loss_v = 0
if global_step % 250 == 0:
if n % 25 == 0:
# Testing accuracy # Testing accuracy
num_testing = 0 accuracy_sum = 0
total = 0 testing_size = 4 # times testing_batch_size = 256
correct = 0 for t, (data, label) in enumerate(testing_loader):
for data, label in testing_loader: if t >= testing_size:
if num_testing > 100:
break break
else:
num_testing += 1 model = model.eval()
data = Variable(data).cuda()
label = Variable(label.long()).cuda()
P = data[:,:,:3] P = data[:,:,:3]
F = data[:,:,3:] F = data[:,:,3:]
out = model((P, F))
offset = int(random.gauss(0, sample_num // 8))
offset = max(offset, -sample_num // 4)
offset = min(offset, sample_num // 4)
sample_num_train = sample_num + offset
# indices = get_indices(batch_size, sample_num_train, point_num)
indices = np.random.choice(P.size()[1], sample_num_train, replace = False).tolist()
P_sampled = P[:,indices,:]
F_sampled = F[:,indices,:]
P_sampled = Variable(P_sampled).cuda()
F_sampled = Variable(F_sampled).cuda()
out = model((P_sampled, F_sampled))
probs = nn.Softmax()(out) probs = nn.Softmax()(out)
# print(probs)
_, pred = probs.max(1) _, pred = probs.max(1)
total += 1 print(pred)
if pred.cpu().data[0] == label.cpu().data[0]: accuracy_sum += torch.mean((pred.data.cpu() == label.long()).float())
correct += 1 accuracy = accuracy_sum / testing_size
accuracy = correct / total
print("accuracy:", accuracy) print("accuracy:", accuracy)
losses.append(loss_v)
accuracies.append(accuracy)
global_step += 1
+88 -44
View File
@@ -8,11 +8,11 @@ import matplotlib.pyplot as plt
try: try:
from .util import knn_indices_func, knn_indices_func_gpu from .util import knn_indices_func, knn_indices_func_gpu
from .layers import MLP, LayerNorm, Conv, SepConv, endchannels from .layers import MLP, LayerNorm, Conv, SepConv, Dense, endchannels
# from .context import timed # from .context import timed
except SystemError: except SystemError:
from util import knn_indices_func, knn_indices_func_gpu from util import knn_indices_func, knn_indices_func_gpu
from layers import MLP, LayerNorm, Conv, SepConv, endchannels from layers import MLP, LayerNorm, Conv, SepConv, Dense, endchannels
# from context import timed # from context import timed
class XConv(nn.Module): class XConv(nn.Module):
@@ -20,8 +20,7 @@ class XConv(nn.Module):
Vectorized pointwise convolution. Vectorized pointwise convolution.
""" """
def __init__(self, C_in, C_out, D, N_neighbors, N_rep, C_lifted = None, def __init__(self, C_in, C_out, D, N_neighbors, N_rep, C_lifted, depth_multiplier):
mlp_width = 2):
""" """
:param C_in: Input dimension of the points' features. :param C_in: Input dimension of the points' features.
:param C_out: Output dimension of the representative point features. :param C_out: Output dimension of the representative point features.
@@ -32,9 +31,6 @@ class XConv(nn.Module):
""" """
super(XConv, self).__init__() super(XConv, self).__init__()
if C_lifted == None:
C_lifted = C_in # Not optimal?
if __debug__: if __debug__:
# Only needed for assertions. # Only needed for assertions.
self.C_in = C_in self.C_in = C_in
@@ -48,9 +44,22 @@ class XConv(nn.Module):
# self.pts_layernorm = LayerNorm(2, momentum = 0.9) # self.pts_layernorm = LayerNorm(2, momentum = 0.9)
# Main dense linear layers # Main dense linear layers
self.mlp_lift = MLP([D] + [self.C_lifted] * (mlp_width - 1), batch_norm = False) self.dense1 = Dense(D, C_lifted)
self.dense2 = Dense(C_lifted, C_lifted)
# Layers to generate X # Layers to generate X
self.x_trans = nn.Sequential(
endchannels(Conv(
in_channels = D,
out_channels = N_neighbors**2,
kernel_size = (1, N_neighbors),
with_bn = False
)),
Dense(N_neighbors**2, N_neighbors**2, with_bn = False),
Dense(N_neighbors**2, N_neighbors**2, with_bn = False, activation = None)
)
"""
self.mid_conv = endchannels(Conv(D, N_neighbors**2, (1, N_neighbors))).cuda() self.mid_conv = endchannels(Conv(D, N_neighbors**2, (1, N_neighbors))).cuda()
self.mid_dwconv1 = endchannels(SepConv( self.mid_dwconv1 = endchannels(SepConv(
in_channels = N_neighbors, in_channels = N_neighbors,
@@ -64,14 +73,15 @@ class XConv(nn.Module):
kernel_size = (1, N_neighbors), kernel_size = (1, N_neighbors),
depth_multiplier = N_neighbors depth_multiplier = N_neighbors
)).cuda() )).cuda()
"""
print(depth_multiplier)
# Final # Final
self.mlp = MLP([N_neighbors] * mlp_width, batch_norm = False)
self.end_conv = endchannels(SepConv( self.end_conv = endchannels(SepConv(
in_channels = C_lifted + C_in, in_channels = C_lifted + C_in,
out_channels = C_out, out_channels = C_out,
kernel_size = (1, N_neighbors), kernel_size = (1, N_neighbors),
depth_multiplier = 4 depth_multiplier = depth_multiplier
)).cuda() )).cuda()
# @timed.timed # @timed.timed
@@ -88,11 +98,16 @@ class XConv(nn.Module):
:return: Features aggregated into point p. :return: Features aggregated into point p.
""" """
p, P, F = x p, P, F = x
assert(p.size()[0] == P.size()[0] == F.size()[0]) # Check N is equal. if F is not None:
assert(p.size()[1] == P.size()[1] == F.size()[1]) # Check N_rep is equal. assert(p.size()[0] == P.size()[0] == F.size()[0]) # Check N is equal.
assert(P.size()[2] == F.size()[2] == self.N_neighbors) # Check N_neighbors is equal. assert(p.size()[1] == P.size()[1] == F.size()[1]) # Check N_rep is equal.
assert(p.size()[2] == P.size()[3] == self.D) # Check D is equal. assert(P.size()[2] == F.size()[2] == self.N_neighbors) # Check N_neighbors is equal.
assert(F.size()[3] == self.C_in) # Check C_in is equal. assert(F.size()[3] == self.C_in) # Check C_in is equal.
else:
assert(p.size()[0] == P.size()[0]) # Check N is equal.
assert(p.size()[1] == P.size()[1]) # Check N_rep is equal.
assert(P.size()[2] == self.N_neighbors) # Check N_neighbors is equal.
assert(p.size()[2] == P.size()[3] == self.D) # Check D is equal.
N = len(P) N = len(P)
N_rep = p.size()[1] N_rep = p.size()[1]
@@ -103,19 +118,28 @@ class XConv(nn.Module):
# P_local = self.pts_layernorm(P - p_center) # P_local = self.pts_layernorm(P - p_center)
# Individually lift each point into C_lifted dim space. # Individually lift each point into C_lifted dim space.
F_lifted = self.mlp_lift(P_local) F_lifted0 = self.dense1(P_local)
F_lifted = self.dense2(F_lifted0)
# Cat F_lifted and F, to size (N, N_rep, N_neighbors, C_lifted + C_in). # Cat F_lifted and F,None to size (N, N_rep, N_neighbors, C_lifted + C_in).
F_cat = torch.cat((F_lifted, F), -1) if F is None:
F_cat = F_lifted
else:
F_cat = torch.cat((F_lifted, F), -1)
# Learn the (N, K, K) X-transformation matrix. # Learn the (N, K, K) X-transformation matrix.
X_shape = (N, N_rep, self.N_neighbors, self.N_neighbors) X_shape = (N, N_rep, self.N_neighbors, self.N_neighbors)
X = self.x_trans(P_local)
X = X.view(*X_shape)
"""
X = self.mid_conv(P_local) X = self.mid_conv(P_local)
X = X.contiguous().view(*X_shape) X = X.contiguous().view(*X_shape)
X = self.mid_dwconv1(X) X = self.mid_dwconv1(X)
X = X.contiguous().view(*X_shape) X = X.contiguous().view(*X_shape)
X = self.mid_dwconv2(X) X = self.mid_dwconv2(X)
X = X.contiguous().view(*X_shape) X = X.contiguous().view(*X_shape)
"""
# Weight and permute F_cat with the learned X. # Weight and permute F_cat with the learned X.
F_X = torch.matmul(X, F_cat) F_X = torch.matmul(X, F_cat)
@@ -127,8 +151,7 @@ class PointCNN(nn.Module):
TODO: Insert documentation TODO: Insert documentation
""" """
def __init__(self, C_in, C_out, D, N_neighbors, dilution, N_rep, def __init__(self, C_in, C_out, D, N_neighbors, dilution, N_rep, r_indices_func):
r_indices_func, C_lifted = None, mlp_width = 2):
""" """
:param C_in: Input dimension of the points' features. :param C_in: Input dimension of the points' features.
:param C_out: Output dimension of the representative point features. :param C_out: Output dimension of the representative point features.
@@ -156,11 +179,12 @@ class PointCNN(nn.Module):
""" """
super(PointCNN, self).__init__() super(PointCNN, self).__init__()
if C_lifted == None: C_lifted = C_out // 2 if C_in == 0 else C_out // 4
C_lifted = C_in # Not optimal? depth_multiplier = min(int(np.ceil(C_out / C_in)), 4)
self.r_indices_func = r_indices_func self.r_indices_func = r_indices_func
self.x_conv = XConv(C_in, C_out, D, N_neighbors, N_rep, C_lifted, mlp_width) self.dense = Dense(C_in, C_out // 2) if C_in != 0 else None
self.x_conv = XConv(C_out // 2 if C_in != 0 else C_in, C_out, D, N_neighbors, N_rep, C_lifted, depth_multiplier)
self.dilution = dilution self.dilution = dilution
def select_region(self, P, P_idx): def select_region(self, P, P_idx):
@@ -193,6 +217,8 @@ class PointCNN(nn.Module):
:return: :return:
""" """
ps, P, F = x ps, P, F = x
t0 = time.time()
F = self.dense(F) if F is not None else F
P_idx = self.r_indices_func(ps, P, self.x_conv.N_neighbors, self.dilution) # This step takes ~97% of the time. P_idx = self.r_indices_func(ps, P, self.x_conv.N_neighbors, self.dilution) # This step takes ~97% of the time.
P_regional = self.select_region(P, P_idx) # Prime target for optimization: KNN on GPU. P_regional = self.select_region(P, P_idx) # Prime target for optimization: KNN on GPU.
if False: if False:
@@ -205,9 +231,12 @@ class PointCNN(nn.Module):
plt.scatter(test_point[0], test_point[1], s = 100, c = 'green') plt.scatter(test_point[0], test_point[1], s = 100, c = 'green')
plt.scatter(neighborhood[:,0], neighborhood[:,1], s = 100, c = 'red') plt.scatter(neighborhood[:,0], neighborhood[:,1], s = 100, c = 'red')
plt.show() plt.show()
F_regional = self.select_region(F, P_idx) t1 = time.time()
# ps, P, F_P -> ps_F F_regional = self.select_region(F, P_idx) if F is not None else F
test_time = time.time() - t1
F_p = self.x_conv((ps, P_regional, F_regional)) F_p = self.x_conv((ps, P_regional, F_regional))
total_time = time.time() - t0
# print("frac of time:", test_time / total_time)
return F_p return F_p
class rPointCNN(nn.Module): class rPointCNN(nn.Module):
@@ -220,7 +249,7 @@ class rPointCNN(nn.Module):
def forward(self, x): def forward(self, x):
P, F = x P, F = x
if self.N_rep < P.size()[1]: if 0 < self.N_rep < P.size()[1]:
idx = np.random.choice(P.size()[1], self.N_rep, replace = False).tolist() idx = np.random.choice(P.size()[1], self.N_rep, replace = False).tolist()
ps = P[:,idx,:] ps = P[:,idx,:]
else: else:
@@ -229,30 +258,45 @@ class rPointCNN(nn.Module):
ps_F = self.pointcnn((ps, P, F)) ps_F = self.pointcnn((ps, P, F))
return ps, ps_F return ps, ps_F
def plot(P, F):
num_F = F.size()[2]
pts = P[0].data.cpu().numpy()
plt.scatter(pts[:,0], pts[:,1], s = num_F, c = "k")
plt.savefig("./%i.png" % num_F)
plt.cla()
if __name__ == "__main__": if __name__ == "__main__":
np.random.seed(0) np.random.seed(0)
N = 1 N = 1
num_points = 500 num_points = 1000
N_rep = 20
D = 2 D = 2
C_in = 16 C_in = 4
C_out = 32 N_neighbors = 10
N_neighbors = 30
dilution = 1 dilution = 1
model = PointCNN(C_in, C_out, D, N_neighbors, dilution, N_rep, knn_indices_func_gpu).cuda() layer1 = rPointCNN(C_in, 8, D, N_neighbors, dilution, 1000, knn_indices_func).cuda()
layer2 = rPointCNN( 8, 16, D, N_neighbors, dilution, 500, knn_indices_func).cuda()
layer3 = rPointCNN( 16, 32, D, N_neighbors, dilution, 250, knn_indices_func).cuda()
layer4 = rPointCNN( 32, 64, D, N_neighbors, dilution, 125, knn_indices_func).cuda()
layer5 = rPointCNN( 64, 128, D, N_neighbors, dilution, 50, knn_indices_func).cuda()
test_P = np.random.rand(N,num_points,D).astype(np.float32) P = np.random.rand(N,num_points,D).astype(np.float32)
test_F = np.random.rand(N,num_points,C_in).astype(np.float32) F = np.random.rand(N,num_points,C_in).astype(np.float32)
idx = np.random.choice(test_P.shape[1], N_rep, replace = False) P = Variable(torch.from_numpy(P)).cuda()
test_ps = test_P[:,idx,:] F = Variable(torch.from_numpy(F)).cuda()
test_P = Variable(torch.from_numpy(test_P)).cuda() if True:
test_F = Variable(torch.from_numpy(test_F)).cuda() P, F = layer1((P, F))
test_ps = Variable(torch.from_numpy(test_ps)).cuda() else:
plot(P, F)
print(test_F.size()) P, F = layer1((P, F))
for _ in range(1): plot(P, F)
out = model((test_ps, test_P, test_F)) P, F = layer2((P, F))
print(out.size()) plot(P, F)
P, F = layer3((P, F))
plot(P, F)
P, F = layer4((P, F))
plot(P, F)
P, F = layer5((P, F))
plot(P, F)
+2 -2
View File
@@ -50,7 +50,7 @@ class Conv(nn.Module):
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, bias = not with_bn) self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, bias = not with_bn)
self.activation = activation self.activation = activation
self.bn = nn.BatchNorm2d(out_channels) if with_bn else None self.bn = nn.BatchNorm2d(out_channels, momentum = 0.9) if with_bn else None
def forward(self, x): def forward(self, x):
x = self.conv(x) x = self.conv(x)
@@ -75,7 +75,7 @@ class SepConv(nn.Module):
) )
self.activation = activation self.activation = activation
self.bn = nn.BatchNorm2d(out_channels) if with_bn else None self.bn = nn.BatchNorm2d(out_channels, momentum = 0.9) if with_bn else None
def forward(self, x): def forward(self, x):
x = self.conv(x) x = self.conv(x)