關(guān)鍵詞
葉根螺栓 時(shí)間序列 LSTM
項(xiàng)目背景
以下直接引用了某些論文的內(nèi)容,幫助大家了解背景。
螺栓連接是風(fēng)力發(fā)電機(jī)組裝配中的重要裝配方式,幾乎涉及到風(fēng)力發(fā)電機(jī)組的所有部件。因此,螺栓的選用和強(qiáng)度校核是風(fēng)力發(fā)電機(jī)組可靠性的重要保證。隨著我國(guó)風(fēng)電事業(yè)的跨越式發(fā)展,伴隨著風(fēng)力發(fā)電成本不斷下降,風(fēng)電機(jī)組的價(jià)格也越來(lái)越低,各大風(fēng)電設(shè)備總裝企業(yè)的價(jià)格戰(zhàn)已經(jīng)進(jìn)行到了白熱化階段。如何在降低成本的情況下,保證風(fēng)電機(jī)組的質(zhì)量,成為各大風(fēng)電企業(yè)面臨的重要問(wèn)題。
--
現(xiàn)階段,我國(guó)風(fēng)電機(jī)組的螺栓失效問(wèn)題已經(jīng)在連接塔筒法蘭的高強(qiáng)度螺栓上有所體現(xiàn)。主要失效形式為:安裝麥搶帶發(fā)生滑絲、扭斷、屈服、甚至拉斷等現(xiàn)象;設(shè)備運(yùn)行過(guò)程中發(fā)生螺栓斷裂,威脅機(jī)組運(yùn)行,嚴(yán)重者甚至造成風(fēng)力發(fā)電機(jī)組倒塌。
--
螺栓斷裂與以下幾種因素有關(guān):
- 螺栓的質(zhì)量
- 螺栓的預(yù)緊力矩
- 螺栓的強(qiáng)度
- 螺栓的疲勞強(qiáng)度
數(shù)據(jù)及問(wèn)題描述
該項(xiàng)目是想基于風(fēng)機(jī)scada系統(tǒng)數(shù)據(jù)通過(guò)機(jī)器學(xué)習(xí)的方式對(duì)葉根螺栓的斷裂進(jìn)行提前預(yù)測(cè),從而避免重大事故和經(jīng)濟(jì)損失。
拿到的樣本數(shù)據(jù)中包括33組風(fēng)機(jī)3月、4月、5月、6月共四個(gè)月的數(shù)據(jù),其中部分?jǐn)?shù)據(jù)有少量缺失。故障風(fēng)機(jī)為17#、12#、6#,其他均為正常風(fēng)機(jī)。根據(jù)實(shí)際數(shù)據(jù)情況與建模需求選取全部故障風(fēng)機(jī)數(shù)據(jù)、隨機(jī)選取適量的正常數(shù)據(jù)進(jìn)行建模。具體數(shù)據(jù)使用情況如下(參與分析風(fēng)機(jī)均已去除停機(jī)數(shù)據(jù)):

解決方案
- 經(jīng)過(guò)對(duì)數(shù)據(jù)的深入考察發(fā)現(xiàn),所給數(shù)據(jù)中,與變槳系統(tǒng)相關(guān)的變量及部分溫度特征與葉根螺栓故障有明顯的相關(guān)性,如平均槳葉角度、最高Topbox溫度、最高控制柜溫度等。理論上基于足夠的數(shù)據(jù)可以訓(xùn)練識(shí)別故障的機(jī)器學(xué)習(xí)模型。
- 考慮到葉根螺栓斷裂在時(shí)間上應(yīng)該存在某種應(yīng)力累積的過(guò)程,達(dá)到一定程度時(shí)則會(huì)發(fā)生斷裂。因此嘗試基于時(shí)間序列的LSTM深度學(xué)習(xí)模型。為了預(yù)測(cè)可能發(fā)生葉根螺栓斷裂的風(fēng)機(jī)大概的斷裂時(shí)間,考慮搭建兩個(gè)模型,第一個(gè)模型識(shí)別是否為可能發(fā)生故障的風(fēng)機(jī)并標(biāo)識(shí)蘊(yùn)含故障模式的數(shù)據(jù)與正常數(shù)據(jù),第二個(gè)模型對(duì)故障風(fēng)機(jī)預(yù)測(cè)發(fā)生斷裂時(shí)間。具體思路如下:
- 首先去除停機(jī)部分?jǐn)?shù)據(jù),僅考察風(fēng)機(jī)運(yùn)行數(shù)據(jù);
- 然后針對(duì)第一階段的模型,對(duì)故障風(fēng)機(jī)停機(jī)時(shí)間點(diǎn)之前的數(shù)據(jù)逐條貼標(biāo)簽1,取時(shí)間窗口為10天,對(duì)故障點(diǎn)t0后的正常運(yùn)行數(shù)據(jù)和隨機(jī)選取的正常風(fēng)機(jī)數(shù)據(jù)貼標(biāo)簽0,分別表示葉根螺栓故障發(fā)生和未發(fā)生。
- 對(duì)上述數(shù)據(jù)進(jìn)行滑窗處理,為使得參與訓(xùn)練數(shù)據(jù)中正負(fù)樣本均衡,令正常數(shù)據(jù)的滑窗步長(zhǎng)大于故障數(shù)據(jù)的滑窗步長(zhǎng)?;谏鲜鰯?shù)據(jù)訓(xùn)練一個(gè)LSTM分類(lèi)模型。
- 針對(duì)第二階段的故障模型,對(duì)故障風(fēng)機(jī)停機(jī)時(shí)間點(diǎn)之前的數(shù)據(jù)逐條貼標(biāo)簽,以遞增的整數(shù)表示距離該時(shí)間點(diǎn)的由近及遠(yuǎn),取時(shí)間窗口為10天,為提高模型預(yù)測(cè)效果,相鄰數(shù)據(jù)之間的標(biāo)簽為間隔為10的整數(shù)。例如對(duì)于10分鐘級(jí)的數(shù)據(jù),某條數(shù)據(jù)的標(biāo)簽為60則表示距離故障發(fā)生時(shí)間t0為1小時(shí)左右。
- 同樣進(jìn)行滑窗處理,一個(gè)滑窗內(nèi)有144條數(shù)據(jù)。訓(xùn)練LSTM回歸模型。
實(shí)施過(guò)程
特征處理
考察樣本數(shù)據(jù),剔除無(wú)關(guān)變量,選取與葉根螺栓斷裂故障相關(guān)的特征。經(jīng)過(guò)分析發(fā)現(xiàn)如下特征與葉根螺栓斷裂故障有一定相關(guān)性:平均槳葉角度(deg)、最小槳葉角度(deg)、最大槳葉角度(deg)、最大機(jī)艙加速度(g)、最高電機(jī)繞組溫度(℃)、最高Topbox溫度(℃)、最高機(jī)艙溫度(℃)、最高控制柜溫度(℃)、最高變槳電機(jī)1溫度(℃)、最高變槳電機(jī)2溫度(℃)、最高變槳電機(jī)3溫度(℃)、最高變槳柜1的柜體溫度(℃)、最高變槳柜2的柜體溫度(℃)、最高變槳柜3的柜體溫度(℃)、平均變流器功率(kW)、最高變槳柜1備電柜溫度(℃)、最高變槳柜2備電柜溫度(℃)、最高變槳柜3備電柜溫度(℃)。
為便于觀察,以下繪制了部分特征在一定時(shí)間范圍內(nèi)的變化趨勢(shì)對(duì)比:




經(jīng)過(guò)對(duì)所有特征的遍歷分析,選取上述特征或其組合參與模型訓(xùn)練,對(duì)最高變槳電機(jī)1溫度(℃)、最高變槳電機(jī)2溫度(℃)、最高變槳電機(jī)3溫度(℃),最高變槳柜1的柜體溫度(℃)、最高變槳柜2的柜體溫度(℃)、最高變槳柜3的柜體溫度(℃),最高變槳柜1備電柜溫度(℃)、最高變槳柜2備電柜溫度(℃)、最高變槳柜3備電柜溫度(℃)等特征進(jìn)行合并(分別取其均值作為新變量代替三個(gè)近似特征)。
基于LSTM的故障分類(lèi)模型
- 模型的結(jié)構(gòu)上,LSTM層設(shè)置cell_size 為13,其中有dropout機(jī)制,會(huì)隨機(jī)忘記前幾層的Cell中神經(jīng)元,該層設(shè)置共13個(gè)神經(jīng)元作為L(zhǎng)STM的輸出。DeepLearnning全連接層共設(shè)置3層,其中第一層有18個(gè)神經(jīng)元,激活函數(shù)為ReLU;第二層有15個(gè)神經(jīng)元,激活函數(shù)為ReLU;第三層有12個(gè)神經(jīng)元,激活函數(shù)也為ReLU。最終隱層的輸出進(jìn)入輸出層,輸出層共有1個(gè)神經(jīng)元,輸出0/1數(shù)值標(biāo)簽。
-
訓(xùn)練方面,采取128Batch Size大小的數(shù)據(jù)分批投入。由于已在數(shù)據(jù)滑窗時(shí)均衡了正負(fù)樣本比例,這里直接按4:1的比例隨機(jī)劃分訓(xùn)練集和測(cè)試集。
訓(xùn)練過(guò)程中的loss下降趨勢(shì)如下圖所示:
損失函數(shù)
經(jīng)過(guò)測(cè)試模型最終的綜合準(zhǔn)確率為96%,故障召回率95%。如下圖:
report
基于LSTM的故障時(shí)間預(yù)測(cè)模型
- 該模型為回歸模型,模型的結(jié)構(gòu)上,LSTM層設(shè)置cell_size 為20,num_size為3,DeepLearnning全連接層,同樣設(shè)置3層,其中第一層有20個(gè)神經(jīng)元,激活函數(shù)為ReLU;第二層有15個(gè)神經(jīng)元,激活函數(shù)為ReLU;第三層有11個(gè)神經(jīng)元,激活函數(shù)也為ReLU。學(xué)習(xí)率為0.01。
- 最終隱層的輸出進(jìn)入輸出層,輸出層共有1個(gè)神經(jīng)元,輸出距離故障停機(jī)時(shí)間點(diǎn)的整數(shù)值標(biāo)簽,激活函數(shù)為L(zhǎng)inear線性激活函數(shù),構(gòu)造出基于LSTM神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)的回歸模型。
-
訓(xùn)練方面,采取64Batch Size大小的數(shù)據(jù)分批投入。Loss的計(jì)算采用均方誤差。其模型架構(gòu)如下圖所示:
模型結(jié)構(gòu)
Loss值變化如下,模型loss最終收斂于232.86,對(duì)于一個(gè)Batch Size(10080條數(shù)據(jù))來(lái)說(shuō)已經(jīng)很低,訓(xùn)練效果比較理想。

將實(shí)際數(shù)據(jù)與預(yù)測(cè)數(shù)據(jù)對(duì)比如下圖,對(duì)于葉根螺栓故障來(lái)說(shuō)該預(yù)測(cè)誤差是可以接受的。

核心代碼
這里貼出部分代碼(關(guān)鍵代碼)
- lstm網(wǎng)絡(luò)實(shí)現(xiàn)(基于tensorflow1.2.0)
# -*- coding: utf-8 -*-
"""
Created on Wed Sep 13 13:20:45 2018
@author: xuanlei
"""
import pandas as pd
import tensorflow as tf
import numpy as np
#==============================================================================
# Batch Normalization
#==============================================================================
def batch_norm_layer(x, train_phase, scope_bn):
with tf.variable_scope(scope_bn):
beta = tf.Variable(tf.constant(0.0, shape=[x.shape[-1]]), name='beta', trainable=True)
gamma = tf.Variable(tf.constant(1.0, shape=[x.shape[-1]]), name='gamma', trainable=True)
axises = np.arange(len(x.shape) - 1)
batch_mean, batch_var = tf.nn.moments(x, axises, name='moments')
ema = tf.train.ExponentialMovingAverage(decay=0.5)
def mean_var_with_update():
ema_apply_op = ema.apply([batch_mean, batch_var])
with tf.control_dependencies([ema_apply_op]):
return tf.identity(batch_mean), tf.identity(batch_var)
mean, var = tf.cond(train_phase, mean_var_with_update,
lambda: (ema.average(batch_mean), ema.average(batch_var)))
normed = tf.nn.batch_normalization(x, mean, var, beta, gamma, 1e-3)
return normed
#==============================================================================
# RNN Structure
#==============================================================================
class LSTMRNN():
#initial setting
def __init__(self, n_steps, input_size, output_size, cell_size, h1_size, h2_size, h3_size, LR,num_size, batch_size):
self.n_steps = n_steps
self.input_size = input_size
self.output_size = output_size
self.cell_size = cell_size
self.num_size = num_size
self.h1_size = h1_size
self.h2_size = h2_size
self.h3_size = h3_size
self.batch_size = batch_size
self.LR = LR
self.num_size = num_size
with tf.name_scope('inputs'):
self.xs = tf.placeholder(tf.float32, [None, n_steps, input_size], name='xs')
self.ys = tf.placeholder(tf.float32, [None, output_size], name='ys')
self.keep_prob = tf.placeholder(tf.float32, name='keep_prob')
self.train_phase = tf.placeholder(tf.bool, name='train_phase')
with tf.name_scope('in_hidden'):
self.add_input_layer()
with tf.name_scope('LSTM_Cell'):
self.add_cell_layer()
with tf.name_scope('hidden_1'):
self.add_h1_layer()
with tf.name_scope('hidden_2'):
self.add_h2_layer()
with tf.name_scope('hidden_3'):
self.add_h3_layer()
with tf.name_scope('out_hidden'):
self.add_output_layer()
with tf.name_scope('cost'):
self.compute_cost()
with tf.name_scope('train'):
self.train_op = tf.train.AdamOptimizer(learning_rate=self.LR).minimize(self.cost)
def add_input_layer(self):
with tf.name_scope('input_layer'):
l_in_x = tf.reshape(self.xs,[-1,self.input_size], name='x_input')
#Ws_in = tf.Variable(tf.truncated_normal([self.input_size, self.cell_size], mean=1, stddev=0.5))
Ws_in = tf.get_variable("W", shape=[self.input_size, self.cell_size],initializer=tf.contrib.layers.xavier_initializer())
bs_in = tf.Variable(tf.zeros([self.cell_size,])+0.01)
l_in_y = tf.matmul(l_in_x,Ws_in)+bs_in
self.l_in_y = tf.reshape(l_in_y,[-1,self.n_steps,self.cell_size],name='cell_input')
def add_cell_layer(self):
with tf.name_scope('LSTM_layer'):
lstm_cell = tf.contrib.rnn.DropoutWrapper(tf.contrib.rnn.BasicLSTMCell(self.cell_size, forget_bias=1.0, state_is_tuple=True), output_keep_prob=self.keep_prob)
lstm_cells = tf.contrib.rnn.MultiRNNCell([lstm_cell]*self.num_size, state_is_tuple=True)
self.cells_init_state = lstm_cells.zero_state(self.batch_size,dtype=tf.float32)
self.cells_outputs, self.cells_final_state = tf.nn.dynamic_rnn(lstm_cells, self.l_in_y, initial_state=self.cells_init_state, time_major=False)
tf.summary.histogram('cells_outputs',self.cells_outputs)
tf.summary.histogram('cells_final_state',self.cells_final_state)
def add_h1_layer(self):
with tf.name_scope('h1_layer'):
h1_x = tf.reshape(self.cells_outputs, [-1,self.cell_size])
#Ws_h1 = tf.Variable(tf.truncated_normal([self.cell_size, self.h1_size], mean=3, stddev=1))
Ws_h1 = tf.get_variable("W1", shape=[self.cell_size, self.h1_size],initializer=tf.contrib.layers.xavier_initializer())
bs_h1 = tf.Variable(tf.zeros([self.h1_size,])+0.01)
non_bn_h1 = tf.nn.relu(tf.matmul(h1_x,Ws_h1)+bs_h1)
non_bn_h1 = tf.matmul(h1_x,Ws_h1)+bs_h1
self.h1_y = batch_norm_layer(non_bn_h1, train_phase=self.train_phase, scope_bn='bn_h1')
def add_h2_layer(self):
with tf.name_scope('h2_layer'):
h2_x = tf.reshape(self.h1_y, [-1,self.h1_size])
#Ws_h2 = tf.Variable(tf.truncated_normal([self.h1_size, self.h2_size], mean=3, stddev=2))
Ws_h2 = tf.get_variable("W2", shape=[self.h1_size, self.h2_size],initializer=tf.contrib.layers.xavier_initializer())
bs_h2 = tf.Variable(tf.zeros([self.h2_size,])+0.01)
non_bn_h2 = tf.nn.relu(tf.matmul(h2_x,Ws_h2)+bs_h2)
non_bn_h2 = tf.matmul(h2_x,Ws_h2)+bs_h2
self.h2_y = batch_norm_layer(non_bn_h2, train_phase=self.train_phase, scope_bn='bn_h2')
def add_h3_layer(self):
with tf.name_scope('h3_layer'):
h3_x = tf.reshape(self.h2_y, [-1,self.h2_size])
#Ws_h3 = tf.Variable(tf.truncated_normal([self.h2_size, self.h3_size], mean=3, stddev=2))
Ws_h3 = tf.get_variable("W3", shape=[self.h2_size, self.h3_size],initializer=tf.contrib.layers.xavier_initializer())
bs_h3 = tf.Variable(tf.zeros([self.h3_size,])+0.01)
non_bn_h3 = tf.nn.relu(tf.matmul(h3_x,Ws_h3)+bs_h3)
non_bn_h3 = tf.matmul(h3_x,Ws_h3)+bs_h3
self.h3_y = batch_norm_layer(non_bn_h3, train_phase=self.train_phase, scope_bn='bn_h3')
def add_output_layer(self):
layer_name='output_layer'
with tf.name_scope('output_layer'):
l_out_x = tf.reshape(self.h3_y,[-1,self.h3_size],name = 'y_input')
#Ws_out = tf.Variable(tf.truncated_normal([self.h3_size, self.output_size], mean=3, stddev=1))
Ws_out = tf.get_variable("W4", shape=[self.h3_size, self.output_size],initializer=tf.contrib.layers.xavier_initializer())
bs_out = tf.Variable(tf.zeros([self.output_size,]))
#self.pred = tf.matmul(l_out_x,Ws_out)+bs_out
self.pred = tf.nn.sigmoid(tf.matmul(l_out_x,Ws_out)+bs_out)
#self.pred = tf.matmul(l_out_x,Ws_out)+bs_out
#self.pred = tf.nn.softmax(tf.matmul(l_out_x,Ws_out)+bs_out)
tf.summary.histogram('w', Ws_out)
tf.summary.histogram('b', bs_out)
tf.summary.histogram('out', self.pred)
#===============================================================================
# 交叉熵
#===============================================================================
def compute_cost(self):
with tf.name_scope('loss'):
self.cost = -tf.reduce_sum(self.ys*tf.log(self.pred+0.001)+(1-self.ys)*tf.log(1-self.pred+0.001))#可以調(diào)節(jié)權(quán)重控制樣本不平衡的數(shù)據(jù)訓(xùn)練
tf.summary.scalar('result_cost', self.cost)
小結(jié)
- 值得指出的是故障記錄中的故障發(fā)生時(shí)間并非實(shí)際的葉根螺栓斷裂時(shí)間而是發(fā)現(xiàn)時(shí)間,盡管真正的斷裂時(shí)間對(duì)模型更有意義但實(shí)際中難以捕捉,因此若進(jìn)一步提升模型的實(shí)際效果,可將貼標(biāo)簽的基準(zhǔn)時(shí)間往前順延。
- 盡管樣本數(shù)據(jù)中存在與葉根螺栓斷裂故障相關(guān)的特征,模型效果較為理想,但并不能排除其他故障或風(fēng)機(jī)自身特性的影響。要訓(xùn)練一個(gè)穩(wěn)健的、高魯棒性的模型還需要更多的故障數(shù)據(jù)。


