ปัญหา LSTM Autoencoder
TLDR:
ตัวเข้ารหัสอัตโนมัติรองรับการสร้างชุดเวลาใหม่และเพียงแค่ทำนายค่าเฉลี่ย
การตั้งคำถาม:
นี่คือสรุปความพยายามของฉันในการเข้ารหัสอัตโนมัติแบบลำดับต่อลำดับ ภาพนี้นำมาจากกระดาษนี้:https://arxiv.org/pdf/1607.00148.pdf
ตัวเข้ารหัส:เลเยอร์ LSTM มาตรฐาน ลำดับอินพุตถูกเข้ารหัสในสถานะสุดท้ายที่ซ่อนอยู่
ตัวถอดรหัส: LSTM Cell (ฉันคิดว่า!) x[N]สร้างองค์ประกอบหนึ่งลำดับในช่วงเวลาที่เริ่มต้นด้วยองค์ประกอบที่ผ่านมา
ขั้นตอนวิธีการถอดรหัสมีดังต่อไปนี้สำหรับลำดับความยาวN:
- รับสถานะการซ่อนเริ่มต้นของตัวถอดรหัส
hs[N]: เพียงใช้สถานะสุดท้ายที่ซ่อนอยู่ของตัวเข้ารหัส x[N]= w.dot(hs[N]) + bสร้างองค์ประกอบสุดท้ายในลำดับ:- รูปแบบเดียวกันสำหรับองค์ประกอบอื่น ๆ :
x[i]= w.dot(hs[i]) + b - ใช้
x[i]และhs[i]เป็นอินพุตLSTMCellเพื่อรับx[i-1]และhs[i-1]
ตัวอย่างการทำงานขั้นต่ำ:
นี่คือการใช้งานของฉันโดยเริ่มจากตัวเข้ารหัส:
class SeqEncoderLSTM(nn.Module):
def __init__(self, n_features, latent_size):
super(SeqEncoderLSTM, self).__init__()
self.lstm = nn.LSTM(
n_features,
latent_size,
batch_first=True)
def forward(self, x):
_, hs = self.lstm(x)
return hs
คลาสตัวถอดรหัส:
class SeqDecoderLSTM(nn.Module):
def __init__(self, emb_size, n_features):
super(SeqDecoderLSTM, self).__init__()
self.cell = nn.LSTMCell(n_features, emb_size)
self.dense = nn.Linear(emb_size, n_features)
def forward(self, hs_0, seq_len):
x = torch.tensor([])
# Final hidden and cell state from encoder
hs_i, cs_i = hs_0
# reconstruct first element with encoder output
x_i = self.dense(hs_i)
x = torch.cat([x, x_i])
# reconstruct remaining elements
for i in range(1, seq_len):
hs_i, cs_i = self.cell(x_i, (hs_i, cs_i))
x_i = self.dense(hs_i)
x = torch.cat([x, x_i])
return x
นำทั้งสองมารวมกัน:
class LSTMEncoderDecoder(nn.Module):
def __init__(self, n_features, emb_size):
super(LSTMEncoderDecoder, self).__init__()
self.n_features = n_features
self.hidden_size = emb_size
self.encoder = SeqEncoderLSTM(n_features, emb_size)
self.decoder = SeqDecoderLSTM(emb_size, n_features)
def forward(self, x):
seq_len = x.shape[1]
hs = self.encoder(x)
hs = tuple([h.squeeze(0) for h in hs])
out = self.decoder(hs, seq_len)
return out.unsqueeze(0)
และนี่คือฟังก์ชั่นการฝึกของฉัน:
def train_encoder(model, epochs, trainload, testload=None, criterion=nn.MSELoss(), optimizer=optim.Adam, lr=1e-6, reverse=False):
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f'Training model on {device}')
model = model.to(device)
opt = optimizer(model.parameters(), lr)
train_loss = []
valid_loss = []
for e in tqdm(range(epochs)):
running_tl = 0
running_vl = 0
for x in trainload:
x = x.to(device).float()
opt.zero_grad()
x_hat = model(x)
if reverse:
x = torch.flip(x, [1])
loss = criterion(x_hat, x)
loss.backward()
opt.step()
running_tl += loss.item()
if testload is not None:
model.eval()
with torch.no_grad():
for x in testload:
x = x.to(device).float()
loss = criterion(model(x), x)
running_vl += loss.item()
valid_loss.append(running_vl / len(testload))
model.train()
train_loss.append(running_tl / len(trainload))
return train_loss, valid_loss
ข้อมูล:
ชุดข้อมูลขนาดใหญ่ของเหตุการณ์ที่ดึงมาจากข่าว (ICEWS) มีหมวดหมู่ต่างๆที่อธิบายแต่ละเหตุการณ์ ตอนแรกฉันเข้ารหัสตัวแปรเหล่านี้แบบร้อนแรงโดยขยายข้อมูลเป็น 274 มิติ อย่างไรก็ตามในการดีบักโมเดลฉันได้ตัดมันลงเป็นลำดับเดียวที่มีความยาว 14 timesteps และมีเพียง 5 ตัวแปรเท่านั้น นี่คือลำดับที่ฉันพยายามจะใส่มากเกินไป:
tensor([[0.5122, 0.0360, 0.7027, 0.0721, 0.1892],
[0.5177, 0.0833, 0.6574, 0.1204, 0.1389],
[0.4643, 0.0364, 0.6242, 0.1576, 0.1818],
[0.4375, 0.0133, 0.5733, 0.1867, 0.2267],
[0.4838, 0.0625, 0.6042, 0.1771, 0.1562],
[0.4804, 0.0175, 0.6798, 0.1053, 0.1974],
[0.5030, 0.0445, 0.6712, 0.1438, 0.1404],
[0.4987, 0.0490, 0.6699, 0.1536, 0.1275],
[0.4898, 0.0388, 0.6704, 0.1330, 0.1579],
[0.4711, 0.0390, 0.5877, 0.1532, 0.2201],
[0.4627, 0.0484, 0.5269, 0.1882, 0.2366],
[0.5043, 0.0807, 0.6646, 0.1429, 0.1118],
[0.4852, 0.0606, 0.6364, 0.1515, 0.1515],
[0.5279, 0.0629, 0.6886, 0.1514, 0.0971]], dtype=torch.float64)
และนี่คือDatasetคลาสที่กำหนดเอง:
class TimeseriesDataSet(Dataset):
def __init__(self, data, window, n_features, overlap=0):
super().__init__()
if isinstance(data, (np.ndarray)):
data = torch.tensor(data)
elif isinstance(data, (pd.Series, pd.DataFrame)):
data = torch.tensor(data.copy().to_numpy())
else:
raise TypeError(f"Data should be ndarray, series or dataframe. Found {type(data)}.")
self.n_features = n_features
self.seqs = torch.split(data, window)
def __len__(self):
return len(self.seqs)
def __getitem__(self, idx):
try:
return self.seqs[idx].view(-1, self.n_features)
except TypeError:
raise TypeError("Dataset only accepts integer index/slices, not lists/arrays.")
ปัญหา:
โมเดลเรียนรู้ค่าเฉลี่ยเท่านั้นไม่ว่าฉันจะสร้างโมเดลที่ซับซ้อนแค่ไหนหรือตอนนี้ฉันฝึกมานานแค่ไหน
คาดการณ์ / สร้างใหม่:
ตามจริง:
งานวิจัยของฉัน:
ปัญหานี้เหมือนกับปัญหาที่กล่าวถึงในคำถามนี้: ตัวเข้ารหัสอัตโนมัติ LSTM จะส่งกลับค่าเฉลี่ยของลำดับอินพุตเสมอ
ปัญหาในกรณีนั้นจบลงด้วยการที่ฟังก์ชันวัตถุประสงค์คือการหาค่าเฉลี่ยของไทม์ซีรีส์เป้าหมายก่อนที่จะคำนวณการสูญเสีย นี่เป็นเพราะข้อผิดพลาดในการออกอากาศเนื่องจากผู้เขียนไม่มีอินพุตขนาดที่เหมาะสมสำหรับฟังก์ชันวัตถุประสงค์
ในกรณีของฉันฉันไม่เห็นว่านี่เป็นปัญหา ฉันได้ตรวจสอบและตรวจสอบอีกครั้งว่ามิติ / ขนาดทั้งหมดของฉันสอดคล้องกัน ฉันกำลังสูญเสีย
สิ่งอื่น ๆ ที่ฉันพยายาม
- ฉันได้ลองแล้วโดยมีความยาวของลำดับที่แตกต่างกันตั้งแต่ 7 timesteps ไปจนถึง 100 time steps
- ฉันได้ลองใช้ตัวแปรหลายตัวในอนุกรมเวลาแล้ว ฉันได้ลองใช้ตัวแปรทั้งหมด 274 ตัวแปรที่ข้อมูลมีอยู่
- ฉันได้ลองใช้
reductionพารามิเตอร์ต่างๆในnn.MSELossโมดูลแล้ว กระดาษเรียกร้องsumแต่ฉันได้ลองทั้งสองอย่างsumและmean. ไม่แตกต่าง. - กระดาษเรียกร้องให้สร้างลำดับใหม่ตามลำดับย้อนกลับ (ดูภาพด้านบน) ฉันได้ลองใช้วิธีนี้โดยใช้
flipudอินพุตต้นฉบับ (หลังจากการฝึกอบรม แต่ก่อนที่จะคำนวณการสูญเสีย) สิ่งนี้ไม่ทำให้เกิดความแตกต่าง - ฉันพยายามทำให้โมเดลซับซ้อนขึ้นโดยการเพิ่มเลเยอร์ LSTM พิเศษในตัวเข้ารหัส
- ฉันได้ลองเล่นกับช่องว่างแฝงแล้ว ฉันได้ลองจาก 50% ของจำนวนคุณสมบัติที่ป้อนเป็น 150%
- ฉันได้ลองใส่ลำดับเดียวมากเกินไปแล้ว (มีให้ในส่วนข้อมูลด้านบน)
คำถาม:
อะไรทำให้แบบจำลองของฉันทำนายค่าเฉลี่ยและฉันจะแก้ไขได้อย่างไร
คำตอบ
เอาล่ะหลังจากการดีบักฉันคิดว่าฉันรู้เหตุผลแล้ว
TLDR
- คุณพยายามคาดคะเนค่าการประทับเวลาถัดไปแทนความแตกต่างระหว่างการประทับเวลาปัจจุบันกับค่าก่อนหน้า
hidden_featuresจำนวนของคุณน้อยเกินไปทำให้แบบจำลองไม่สามารถใส่ได้แม้แต่ตัวอย่างเดียว
การวิเคราะห์
รหัสที่ใช้
เริ่มต้นด้วยรหัส (รุ่นเดียวกัน):
import seaborn as sns
import matplotlib.pyplot as plt
def get_data(subtract: bool = False):
# (1, 14, 5)
input_tensor = torch.tensor(
[
[0.5122, 0.0360, 0.7027, 0.0721, 0.1892],
[0.5177, 0.0833, 0.6574, 0.1204, 0.1389],
[0.4643, 0.0364, 0.6242, 0.1576, 0.1818],
[0.4375, 0.0133, 0.5733, 0.1867, 0.2267],
[0.4838, 0.0625, 0.6042, 0.1771, 0.1562],
[0.4804, 0.0175, 0.6798, 0.1053, 0.1974],
[0.5030, 0.0445, 0.6712, 0.1438, 0.1404],
[0.4987, 0.0490, 0.6699, 0.1536, 0.1275],
[0.4898, 0.0388, 0.6704, 0.1330, 0.1579],
[0.4711, 0.0390, 0.5877, 0.1532, 0.2201],
[0.4627, 0.0484, 0.5269, 0.1882, 0.2366],
[0.5043, 0.0807, 0.6646, 0.1429, 0.1118],
[0.4852, 0.0606, 0.6364, 0.1515, 0.1515],
[0.5279, 0.0629, 0.6886, 0.1514, 0.0971],
]
).unsqueeze(0)
if subtract:
initial_values = input_tensor[:, 0, :]
input_tensor -= torch.roll(input_tensor, 1, 1)
input_tensor[:, 0, :] = initial_values
return input_tensor
if __name__ == "__main__":
torch.manual_seed(0)
HIDDEN_SIZE = 10
SUBTRACT = False
input_tensor = get_data(SUBTRACT)
model = LSTMEncoderDecoder(input_tensor.shape[-1], HIDDEN_SIZE)
optimizer = torch.optim.Adam(model.parameters())
criterion = torch.nn.MSELoss()
for i in range(1000):
outputs = model(input_tensor)
loss = criterion(outputs, input_tensor)
loss.backward()
optimizer.step()
optimizer.zero_grad()
print(f"{i}: {loss}")
if loss < 1e-4:
break
# Plotting
sns.lineplot(data=outputs.detach().numpy().squeeze())
sns.lineplot(data=input_tensor.detach().numpy().squeeze())
plt.show()
มันทำอะไร:
get_dataทำงานกับข้อมูลที่คุณระบุถ้าsubtract=Falseหรือ (ถ้าsubtract=True) มันลบค่าของการประทับเวลาก่อนหน้าออกจากการประทับเวลาปัจจุบัน- โค้ดที่เหลือจะปรับโมเดลให้เหมาะสมจนกว่าจะ
1e-4ถึงจุดสูญเสีย (ดังนั้นเราสามารถเปรียบเทียบได้ว่าความจุของโมเดลและการเพิ่มขึ้นช่วยได้อย่างไรและจะเกิดอะไรขึ้นเมื่อเราใช้ความแตกต่างของการประทับเวลาแทนการประทับเวลา)
เราจะเปลี่ยนแปลงHIDDEN_SIZEและSUBTRACTพารามิเตอร์เท่านั้น!
ไม่มีสารเสพติดรุ่นเล็ก
HIDDEN_SIZE=5SUBTRACT=False
ในกรณีนี้เราจะได้เส้นตรง โมเดลไม่สามารถปรับให้พอดีและเข้าใจปรากฏการณ์ที่นำเสนอในข้อมูลได้ (ด้วยเหตุนี้เส้นแบนที่คุณกล่าวถึง)
ถึงขีด จำกัด การทำซ้ำ 1,000 ครั้ง
SUBTRACT รุ่นเล็ก
HIDDEN_SIZE=5SUBTRACT=True
ขณะนี้เป้าหมายอยู่ไกลจากเส้นแบนแต่โมเดลไม่สามารถใส่ได้เนื่องจากความจุน้อยเกินไป
ถึงขีด จำกัด การทำซ้ำ 1,000 ครั้ง
ไม่มีสารเสพติดรุ่นที่ใหญ่กว่า
HIDDEN_SIZE=100SUBTRACT=False
มันดีขึ้นมากและเป้าหมายของเราก็โดนโจมตีตาม942ขั้นตอน ไม่มีเส้นแบนอีกต่อไปความจุของรุ่นดูเหมือนจะค่อนข้างดี (สำหรับตัวอย่างเดียวนี้!)
SUBTRACT รุ่นใหญ่
HIDDEN_SIZE=100SUBTRACT=True
แม้ว่ากราฟจะดูไม่สวย แต่เราก็ต้องสูญเสียที่ต้องการหลังจาก215การทำซ้ำเท่านั้น
สุดท้าย
- มักจะใช้ความแตกต่างของ timesteps แทน timesteps (หรือบางส่วนการเปลี่ยนแปลงอื่นดูได้ที่นี่สำหรับข้อมูลเพิ่มเติมเกี่ยวกับที่) ในกรณีอื่นโครงข่ายประสาทเทียมจะพยายาม ... คัดลอกผลลัพธ์จากขั้นตอนก่อนหน้า (ซึ่งเป็นสิ่งที่ง่ายที่สุดที่จะทำ) minima บางส่วนจะพบในลักษณะนี้และการออกไปจากที่นั่นจะต้องใช้ความจุมากขึ้น
- เมื่อคุณใช้ความแตกต่างระหว่างการประทับเวลาไม่มีทางที่จะ "คาดการณ์" แนวโน้มจากระยะเวลาก่อนหน้านี้ได้ โครงข่ายประสาทเทียมต้องเรียนรู้ว่าฟังก์ชันแตกต่างกันอย่างไร
- ใช้โมเดลที่ใหญ่กว่า (สำหรับชุดข้อมูลทั้งหมดคุณควรลองอย่างที่
300ฉันคิด) แต่คุณสามารถปรับแต่งได้ - อย่าใช้
flipud. ใช้ LSTM แบบสองทิศทางด้วยวิธีนี้คุณจะได้รับข้อมูลจากการส่งผ่านไปข้างหน้าและข้างหลังของ LSTM (เพื่อไม่ให้สับสนกับ backprop!) นอกจากนี้ยังควรเพิ่มคะแนนของคุณ
คำถาม
โอเคคำถามที่ 1: คุณกำลังบอกว่าสำหรับตัวแปร x ในอนุกรมเวลาฉันควรฝึกโมเดลให้เรียนรู้ x [i] - x [i-1] แทนค่า x [i]? ฉันตีความถูกต้องหรือไม่?
ใช่แน่นอน ความแตกต่างจะลบแรงกระตุ้นของเครือข่ายประสาทเทียมให้เป็นฐานของการคาดการณ์ในช่วงเวลาที่ผ่านมามากเกินไป (โดยเพียงแค่รับค่าสุดท้ายและอาจเปลี่ยนแปลงเล็กน้อย)
คำถาม 2: คุณบอกว่าการคำนวณของฉันสำหรับคอขวดเป็นศูนย์ไม่ถูกต้อง แต่ตัวอย่างเช่นสมมติว่าฉันใช้เครือข่ายหนาแน่นธรรมดาเป็นตัวเข้ารหัสอัตโนมัติ การได้รับคอขวดที่ถูกต้องขึ้นอยู่กับข้อมูล แต่ถ้าคุณทำให้คอขวดมีขนาดเท่ากับอินพุตคุณจะได้รับฟังก์ชันเอกลักษณ์
ใช่สมมติว่าไม่มีความไม่เป็นเชิงเส้นที่เกี่ยวข้องซึ่งจะทำให้สิ่งนี้ยากขึ้น (ดูที่นี่สำหรับกรณีที่คล้ายกัน) ในกรณีของ LSTMs มี non-linearites นั่นคือจุดหนึ่ง
อีกประการหนึ่งคือเรากำลังสะสมtimestepsในสถานะตัวเข้ารหัสเดียว โดยพื้นฐานแล้วเราจะต้องสะสมtimestepsข้อมูลประจำตัวให้เป็นสถานะที่ซ่อนอยู่และเซลล์เดียวซึ่งไม่น่าเป็นไปได้สูง
ประเด็นสุดท้ายขึ้นอยู่กับความยาวของลำดับ LSTM มีแนวโน้มที่จะลืมข้อมูลที่เกี่ยวข้องน้อยที่สุด (นั่นคือสิ่งที่พวกเขาออกแบบมาให้ทำไม่ใช่แค่จำทุกอย่าง) จึงไม่น่าจะเกิดขึ้นได้อีก
num_features * num_timesteps ไม่ใช่คอขวดที่มีขนาดเท่ากับอินพุตหรือไม่ดังนั้นจึงไม่ควรอำนวยความสะดวกให้โมเดลเรียนรู้เอกลักษณ์
เป็น แต่สมมติว่าคุณมีnum_timestepsจุดข้อมูลแต่ละจุดซึ่งไม่ค่อยเป็นเช่นนั้นอาจอยู่ที่นี่ เกี่ยวกับข้อมูลประจำตัวและเหตุใดจึงยากที่จะทำกับความไม่เป็นเชิงเส้นสำหรับเครือข่ายตามคำตอบข้างต้น
ประเด็นสุดท้ายเกี่ยวกับฟังก์ชันเอกลักษณ์ ถ้าพวกเขาเรียนรู้ได้ง่ายจริง ๆResNetสถาปัตยกรรมก็ไม่น่าจะประสบความสำเร็จ เครือข่ายสามารถรวมตัวกันเป็นตัวตนและทำการ "แก้ไขเล็ก ๆ " ไปยังเอาต์พุตโดยไม่ใช้เครือข่ายซึ่งไม่เป็นเช่นนั้น
ฉันอยากรู้เกี่ยวกับข้อความนี้: "ใช้ความแตกต่างของเวลาแทนการประทับเวลาเสมอ" ดูเหมือนว่าจะมีเอฟเฟกต์การทำให้เป็นมาตรฐานโดยการนำคุณสมบัติทั้งหมดเข้ามาใกล้กัน แต่ฉันไม่เข้าใจว่าเหตุใดจึงเป็นกุญแจสำคัญ การมีแบบจำลองที่ใหญ่ขึ้นดูเหมือนจะเป็นวิธีแก้ปัญหาและสารทดแทนก็ช่วยได้
ที่สำคัญคือการเพิ่มความจุของโมเดล เคล็ดลับการลบขึ้นอยู่กับข้อมูลจริงๆ ลองจินตนาการถึงสถานการณ์ที่รุนแรง:
- เรามีการ
100ประทับเวลาคุณลักษณะเดียว - ค่าการประทับเวลาเริ่มต้นคือ
10000 - ค่าการประทับเวลาอื่น ๆ จะแตกต่างกันไปโดย
1มาก
โครงข่ายประสาทเทียมจะทำอะไร (ง่ายที่สุดที่นี่) คืออะไร? มันอาจจะทิ้ง1การเปลี่ยนแปลงนี้หรือเล็กลงเป็นสัญญาณรบกวนและเพียงแค่คาดการณ์1000สำหรับสิ่งเหล่านี้ทั้งหมด (โดยเฉพาะอย่างยิ่งถ้ามีการกำหนดมาตรฐานบางอย่างไว้) เนื่องจากการปิดโดย1/1000ไม่มากนัก
จะเกิดอะไรขึ้นถ้าเราลบ? การสูญเสียโครงข่ายประสาทเทียมทั้งหมดอยู่ใน[0, 1]ระยะขอบของการประทับเวลาแต่ละครั้งแทนที่จะเป็น[0, 1001]เช่นนั้นจึงรุนแรงกว่าที่จะผิด
และใช่มันเชื่อมต่อกับการทำให้เป็นมาตรฐานในแง่หนึ่งมาลองคิดดู