LightGBM使用简单介绍

王 茂南 2019年6月23日07:27:32
评论
1 16346字阅读54分29秒
摘要这一篇介绍一下关于LightGBM的简单使用方式。主要介绍一下各个参数的含义和一些简单的例子,简单的功能。

简介

这一篇会介绍关于LightGBM的简单的使用,原理方面这一篇不会涉及,会在别的文章里进行介绍。

LightGBM使用简单介绍

参考资料

参数介绍

导入数据常用参数

  • feature_name (list of strings or 'auto'optional (default="auto")) – Feature names. If 'auto' and data is pandas DataFrame, data columns names are used.
  • free_raw_data (booloptional(default=True)) – If True, raw data is freed after constructing inner Dataset.(用来释放内存)
  • weight (listnumpy 1-D arraypandas Series or Noneoptional(default=None)) – Weight for each instance.(每个样本的权重)
  • reference (Dataset or Noneoptional(default=None)) – If this is Dataset for validation, training data should be used as reference.(如果数据用来做validation, training data应该作为reference)

模型常用参数介绍

这里就介绍一下常用的参数,具体的参数说明可以查看链接 : Parameters中文参数说明

  • objective(任务类型)
    • regression(回归)
    • binary, binary log loss classification (or logistic regression). Requires labels in {0, 1}; see cross-entropy application for general probability labels in [0, 1](0,1二分类)
  • learning_rate, default = 0.1, type = double, aliases: shrinkage_rateeta, constraints: learning_rate > 0.0
  • num_leaves , default = 31, type = int, aliases: num_leafmax_leavesmax_leaf, constraints: num_leaves > 1
    • max number of leaves in one tree(一个树的最大叶子节点, 越多分类效果越好,但是会容易出现过拟合的现象)
    • 下面是一个层数和叶子节点个数的关系(这里假设是满树, 一般层数不要超过7层,但因为可能不是满树,所有100+的叶子节点树可能会很深)
LightGBM使用简单介绍
  • metric(计算误差的函数)
    • l1, absolute loss, aliases: mean_absolute_errormaeregression_l1
    • l2, square loss, aliases: mean_squared_errormseregression_l2regression
    • l2_root, root square loss, aliases: root_mean_squared_errorrmse
    • aucAUC
    • binary_loglosslog loss, aliases: binary
  • feature_fraction(特征的选择), default = 1.0, type = double, aliases: sub_featurecolsample_bytree, constraints: 0.0 < feature_fraction <= 1.0
    • LightGBM will randomly select part of features on each iteration if feature_fraction smaller than 1.0. For example, if you set it to 0.8, LightGBM will select 80% of features before training each tree.(不会使用全部的特征进行训练,会选择部分特征进行训练)
    • can be used to speed up training(加快训练速度)
    • can be used to deal with over-fitting(防止出现过拟合)
  • feature_fraction_seed, default = 2, type = int
    • random seed for feature_fraction
    • 可以用来做模型的融合(两个模型选择的特征是不一样的)
  • bagging_fraction(数据的选择), default = 1.0, type = double, aliases: sub_rowsubsamplebagging, constraints: 0.0 < bagging_fraction <= 1.0
    • like feature_fraction, but this will randomly select part of data without resampling(训练的时候会选择部分数据进行训练, 且不会重复取值, 这里代表的是每次迭代的时候使用的数据的比例)
    • can be used to speed up training
    • can be used to deal with over-fitting
    • Note: to enable bagging, bagging_freq should be set to a non zero value as well
  • bagging_freq, default = 0, type = int, aliases: subsample_freq
    • frequency for bagging(每次)
    • 0 means disable bagging; k means perform bagging at every k iteration
    • Note: to enable bagging, bagging_fraction should be set to value smaller than 1.0 as well

训练常用参数

  • num_iterations(迭代次数), default = 100, type = int, aliases: num_iterationn_iternum_treenum_treesnum_roundnum_roundsnum_boost_roundn_estimators, constraints: num_iterations >= 0
    • number of boosting iterations
  • categorical_feature(用来指定哪些是类别特征), default = "", type = multi-int or string, aliases: cat_featurecategorical_columncat_column
    • used to specify categorical features
    • use number for index, e.g. categorical_feature=0,1,2 means column_0, column_1 and column_2 are categorical features(使用的方式)
    • add a prefix name: for column name, e.g. categorical_feature=name:c1,c2,c3means c1, c2 and c3 are categorical features
    • Note: only supports categorical with int type(只支持int数据类型)
    • Note: index starts from 0 and it doesn't count the label column when passing type is int
    • Note: all values should be less than Int32.MaxValue (2147483647)
    • Note: using large values could be memory consuming. Tree decision rule works best when categorical features are presented by consecutive integers starting from zero
    • Note: all negative values will be treated as missing values

加快训练速度的建议

LightGBM使用简单介绍

获得更高的准确率

LightGBM使用简单介绍

防止过拟合

LightGBM使用简单介绍

其中的第三条第五条通常会有较好的效果。

LightGBM例子说明

这一部分简单说明一下LightGBM的具体的使用。所有内容来源于官方的例子。Python-package Examples

Simple LightGBM Example(Regression)

这一部分是一个简单的LightGBM来做回归的例子。在这里主要说明下面的几个问题。

  • 创建数据集(1. 导入数据集, 2. 创建LightGBM的dataset)
  • 基本的训练和预测(参数的设置)
    • 在训练过程中进行测试
    • 提前停止训练
  • 将模型保存到文件(保存为txt文件)

准备工作及创建数据集

首先做一下准备工作。

  1. import lightgbm as lgb
  2. import pandas as pd
  3. from sklearn.metrics import mean_squared_error

接着创建数据集,导入数据并创建lightgbm的dataset.

  1. df_train = pd.read_csv('../regression/regression.train', header=None, sep='\t')
  2. df_test = pd.read_csv('../regression/regression.test', header=None, sep='\t')
LightGBM使用简单介绍

查看一下数据集的大概的内容和大小。

  1. # -----------
  2. # 切分数据集
  3. # -----------
  4. x_train = df_train.drop(0,axis=1) # 获得训练集的x
  5. y_train = df_train[0] # 获取训练集的y
  6. x_test = df_test.drop(0,axis=1) # 获取测试集的x
  7. y_test = df_test[0] # 获取测试集的y
  8. # ------------------------------
  9. # create dataset for lightgbm
  10. # ------------------------------
  11. lgb_train = lgb.Dataset(x_train, y_train)
  12. lgb_eval = lgb.Dataset(x_test, y_test, reference=lgb_train)

 模型的训练和模型的保存

下面进行参数的设置,模型的训练和模型的保存。

首先进行参数的设置

  1. params = {
  2.     'boosting_type': 'gbdt',
  3.     'objective': 'regression',
  4.     'metric': {'l2', 'l1'}, # l1和l2代表两种误差计算
  5.     'num_leaves': 31,
  6.     'learning_rate': 0.05,
  7.     'feature_fraction': 0.9,
  8.     'bagging_fraction': 0.8,
  9.     'bagging_freq': 5,
  10.     'verbose': 0
  11. }

开始模型的训练。传入设定的参数,训练的数据,验证的数据。

  1. gbm = lgb.train(params,
  2.                lgb_train,
  3.                num_boost_round=20,
  4.                valid_sets=lgb_eval,
  5.                early_stopping_rounds=5)
LightGBM使用简单介绍

打印的结果,l1和l2分别表示两个误差的计算结果。

训练完毕之后,即可以进行模型的保存

  1. # 保存模型
  2. gbm.save_model('Regressionmodel.txt')

 模型的预测

最后,使用我们训练完毕的模型来进行预测。预测的输入可以是dataframe的格式。

  1. y_pred = gbm.predict(x_test, num_iteration=gbm.best_iteration)

Simple LightGBM Example(Classification)

上面介绍了关于回归的模型, 这里介绍一下多分类模型的使用的方式. 我们就主要介绍一下不同的地方.

参数的设置

对于多分类的模型, 主要关注的是objective, metric, num_class这三个参数. 特别是要注意的是, 我们需要设置num_class, 即类别的个数.

  1. params = {
  2.     'boosting_type': 'gbdt',
  3.     'objective': 'multiclass',
  4.     'num_class':6,
  5.     'metric': {'multi_logloss'}, # l1和l2代表两种误差计算
  6.     'num_leaves': 15,
  7.     'learning_rate': 0.1,
  8.     'feature_fraction': 0.9,
  9.     'bagging_fraction': 0.8,
  10.     'bagging_freq': 5,
  11.     'verbose': 0
  12. }

之后关于训练的方式就是和上面是一样的了. 下面看一下查看每一个特征的重要度的方式.

查看特征的重要度

  1. # 打印特征的重要度
  2. gbm.feature_importance()

Advanced LightGBM Example

这一部分是一个关于LightGBM进一步使用的例子(还是使用官网的例子进行说明)。主要介绍以下的内容。

  • 初始设置样本权重
  • 模型的三种保存方式(1. 保存为txt格式, 2. 保存为json格式, 3. 使用pickle保存)
  • 模型的继续训练(将保存的模型重新导入, 继续训练)
    • 训练过程中逐渐减小learning rate
    • 训练过程中修改参数
    • 训练过程中使用自定义目标函数评估函数
  • 打印特征重要程度(设置特征的名字)

导入样本数据并设置权重

准备工作,导入相应的库。

  1. import json
  2. import lightgbm as lgb
  3. import pandas as pd
  4. import numpy as np
  5. from sklearn.metrics import mean_squared_error
  6. try:
  7.     import cPickle as pickle
  8. except BaseException:
  9.     import pickle

首先我们导入训练数据,并给每一个instance设置权重.

  1. # 读入数据
  2. df_train = pd.read_csv('../binary_classification/binary.train', header=None, sep='\t')
  3. df_test = pd.read_csv('../binary_classification/binary.test', header=None, sep='\t')
  4. # 读入一个权重数据
  5. W_train = pd.read_csv('../binary_classification/binary.train.weight', header=None)[0]
  6. W_test = pd.read_csv('../binary_classification/binary.test.weight', header=None)[0]
LightGBM使用简单介绍

可以看到设置权重的大小和样本个数是一样的,且目前权重都设置为1。接着我们创建对应的数据集。

  1. y_train = df_train[0]
  2. y_test = df_test[0]
  3. x_train = df_train.drop(0, axis=1)
  4. x_test = df_test.drop(0, axis=1)
  5. # 创建数据集
  6. lgb_train = lgb.Dataset(x_train, y_train, weight=W_train, free_raw_data=False)
  7. lgb_eval = lgb.Dataset(x_test, y_test, weight=W_test, free_raw_data=False, reference=lgb_train)

模型的训练与保存

接着我们简单将模型训练以下,并进行保存。首先我们设置参数。

  1. params = {
  2.     'boosting_type': 'gbdt',
  3.     'objective': 'binary',
  4.     'metric': ['binary_logloss','auc'],
  5.     'num_leaves': 31,
  6.     'learning_rate': 0.05,
  7.     'feature_fraction': 0.9,
  8.     'bagging_fraction': 0.8,
  9.     'bagging_freq': 5,
  10.     'verbose': 0
  11. }
  12. # 产生feature name => 用来训练的时候给feature起名字
  13. num_train, num_feature = x_train.shape
  14. feature_name = ['feature_' + str(col) for col in range(num_feature)]
  15. print(feature_name)
  16. """
  17. ['feature_0', 'feature_1', 'feature_2', 'feature_3', 'feature_4', 'feature_5', 'feature_6', 'feature_7', 'feature_8', 'feature_9', 'feature_10', 'feature_11', 'feature_12', 'feature_13', 'feature_14', 'feature_15', 'feature_16', 'feature_17', 'feature_18', 'feature_19', 'feature_20', 'feature_21', 'feature_22', 'feature_23', 'feature_24', 'feature_25', 'feature_26', 'feature_27']
  18. """

接着我们进行训练,这次打印一下auc。

  1. # 模型的训练
  2. gbm = lgb.train(params,
  3.                lgb_train,
  4.                num_boost_round=10,
  5.                valid_sets = lgb_eval,
  6.                feature_name=feature_name,
  7.                categorical_feature=[21])
LightGBM使用简单介绍

接下来就是模型的保存。在这里我们会介绍三种模型保存的方式。

  • 模型保存方式一 : 保存为txt格式
  1. # 模型的保存--保存方式1
  2. gbm.save_model('BinaryModel.txt')
  3. # 加载保存的模型
  4. bst = lgb.Booster(model_file='BinaryModel.txt')
  5. # 这样保存只能保存最好的一个iteration
  6. # can only predict with the best iteration (or the saving iteration)
  7. y_pred = bst.predict(x_test)
  • 模型保存方式二 : 保存为json格式
  1. # 将模型转为json格式, 并保存到文件--保存方式2
  2. model_json = gbm.dump_model()
  3. with open('BinaryModel.json','w+') as f:
  4.     json.dump(model_json, f, indent=4)
  • 模型保存方式三 : 使用pickle进行保存
  1. # 模型的保存, 使用pickle
  2. # dump model with pickle
  3. with open('model.pkl', 'wb') as fout:
  4.     pickle.dump(gbm, fout)
  5. # load model with pickle to predict
  6. with open('model.pkl', 'rb') as fin:
  7.     pkl_bst = pickle.load(fin)
  8. # can predict with any iteration when loaded in pickle way
  9. # 指定使用某一次迭代的结果进行预测
  10. y_pred = pkl_bst.predict(x_test, num_iteration=7)

模型的继续训练

我们可以对保存的模型进行继续训练。我们使用init_model来完成模型的继续训练。

  1. # 10-20次训练
  2. gbm = lgb.train(params,
  3.                lgb_train,
  4.                num_boost_round=10,
  5.                init_model='BinaryModel.txt',
  6.                valid_sets=lgb_eval,
  7.                categorical_feature=[21])
LightGBM使用简单介绍

我们可以看到这里是继续从11-20开始训练,上面第一次我们是训练了10个iterations。

训练过程逐步减少lr

同时在训练的过程中,我们还可以设置学习率是逐步减小的。

  1. # 20-30次训练
  2. # 学习率递减
  3. gbm = lgb.train(params,
  4.                lgb_train,
  5.                num_boost_round=10,
  6.                init_model=gbm,
  7.                learning_rates=lambda iter:0.05 * (0.99**iter),
  8.                valid_sets=lgb_eval,
  9.                categorical_feature=[21])
LightGBM使用简单介绍

训练过程修改参数

除了上面的学习率可以变化之外,我们还可以对参数其他参数进行修改。如这里我们修改了bagging_fraction的值。

  1. # 30-40次训练
  2. # 在训练过程中修改参数
  3. gbm = lgb.train(params,
  4.                lgb_train,
  5.                num_boost_round=10,
  6.                init_model=gbm,
  7.                valid_sets=lgb_eval,
  8.                categorical_feature=[21],
  9.                callbacks=[lgb.reset_parameter(bagging_fraction=[0.7]*5 + [0.6]*5)])
LightGBM使用简单介绍

自定义目标函数和损失函数

  1. # 40-50次训练
  2. # 自定义目标函数
  3. def loglikelihood(preds, train_data):
  4.     labels = train_data.get_label()
  5.     preds = 1. / (1. + np.exp(-preds))
  6.     grad = preds - labels
  7.     hess = preds * (1. - preds)
  8.     return grad, hess
  9. # 自定义损失函数
  10. def binary_error(preds, train_data):
  11.     labels = train_data.get_label()
  12.     return 'error', np.mean(labels != (preds > 0.5)), False

使用上述的损失函数。

  1. gbm = lgb.train(params,
  2.                lgb_train,
  3.                num_boost_round=10,
  4.                init_model=gbm,
  5.                fobj=loglikelihood, # 自定义目标函数
  6.                feval=binary_error, # 自定义评估函数
  7.                valid_sets=lgb_eval,
  8.                categorical_feature=[21])
LightGBM使用简单介绍

模型特征重要度分析

做完上面的工作之后,我们可以分析一下模型的特征的重要程度。

  1. # 打印feature name
  2. print('Feature names : ', gbm.feature_name())
  3. """
  4. Feature names :  ['feature_0', 'feature_1', 'feature_2', 'feature_3', 'feature_4', 'feature_5', 'feature_6', 'feature_7', 'feature_8', 'feature_9', 'feature_10', 'feature_11', 'feature_12', 'feature_13', 'feature_14', 'feature_15', 'feature_16', 'feature_17', 'feature_18', 'feature_19', 'feature_20', 'feature_21', 'feature_22', 'feature_23', 'feature_24', 'feature_25', 'feature_26', 'feature_27']
  5. """

这里的feature name是我们最初的时候就设定的。下面打印一下特征的重要程度。

  1. print('Feature importances : ', list(gbm.feature_importance()))
  2. """
  3. Feature importances :  [67, 31, 11, 86, 19, 173, 26, 18, 9, 57, 16, 19, 4, 50, 40, 14, 0, 38, 15, 27, 5, 0, 160, 15, 133, 201, 130, 136]
  4. """

网格搜索(lightGBM与Sklearn结合)

这一部分单独讲一下将lightGBM与sklearn进行结合,使用GridSearchCV来进行参数的寻找。下面还是具体讲一个例子来进行说明。

例子参考来源 : Microsoft LightGBM with parameter tuning (~0.823)

导入库

  1. import numpy as np
  2. import pandas as pd
  3. import lightgbm as lgb
  4. import matplotlib.pyplot as plt
  5. from sklearn.model_selection import train_test_split
  6. from sklearn.metrics import mean_squared_error
  7. from sklearn.model_selection import GridSearchCV

导入数据集, 创建dataset

  1. # 读入数据
  2. df_train = pd.read_csv('../binary_classification/binary.train', header=None, sep='\t')
  3. df_test = pd.read_csv('../binary_classification/binary.test', header=None, sep='\t')
  4. y_train = df_train[0]
  5. y_test = df_test[0]
  6. x_train = df_train.drop(0, axis=1)
  7. x_test = df_test.drop(0, axis=1)
  8. # 创建数据集
  9. lgb_train = lgb.Dataset(x_train, y_train, free_raw_data=False)
  10. lgb_eval = lgb.Dataset(x_test, y_test, free_raw_data=False, reference=lgb_train)

定义模型参数和搜索空间

首先我们将模型需要使用的参数存在字典中,方便我们之后的使用。

  1. params = {'boosting_type': 'gbdt',
  2.           'max_depth' : -1,
  3.           'objective': 'binary',
  4.           'nthread': 3, # Updated from nthread
  5.           'num_leaves': 64,
  6.           'learning_rate': 0.05,
  7.           'max_bin': 512,
  8.           'subsample_for_bin': 200,
  9.           'subsample': 1,
  10.           'subsample_freq': 1,
  11.           'colsample_bytree': 0.8,
  12.           'reg_alpha': 5,
  13.           'reg_lambda': 10,
  14.           'min_split_gain': 0.5,
  15.           'min_child_weight': 1,
  16.           'min_child_samples': 5,
  17.           'scale_pos_weight': 1,
  18.           'num_class' : 1,
  19.           'metric' : 'binary_error'}

接着,我们定义需要搜索的参数的范围。

  1. gridParams = {
  2.     'learning_rate': [0.005],
  3.     'n_estimators': [40],
  4.     'num_leaves': [6,8,12,16],
  5.     'boosting_type' : ['gbdt'],
  6.     'objective' : ['binary'],
  7.     'random_state' : [501], # Updated from 'seed'
  8.     'colsample_bytree' : [0.65, 0.66],
  9.     'subsample' : [0.7,0.75],
  10.     'reg_alpha' : [1,1.2],
  11.     'reg_lambda' : [1,1.2,1.4],
  12.     }

最后,我们定义模型,在这里传入参数不能传入dict作为参数, 需要取出每个参数对应的值。

  1. mdl = lgb.LGBMClassifier(boosting_type= 'gbdt',
  2.           objective = 'binary',
  3.           n_jobs = 3, # Updated from 'nthread'
  4.           silent = True,
  5.           max_depth = params['max_depth'],
  6.           max_bin = params['max_bin'],
  7.           subsample_for_bin = params['subsample_for_bin'],
  8.           subsample = params['subsample'],
  9.           subsample_freq = params['subsample_freq'],
  10.           min_split_gain = params['min_split_gain'],
  11.           min_child_weight = params['min_child_weight'],
  12.           min_child_samples = params['min_child_samples'],
  13.           scale_pos_weight = params['scale_pos_weight'])

 参数的搜索, 保存最优参数

接下来就可以进行参数的搜索

  1. # Create the grid
  2. grid = GridSearchCV(mdl, gridParams,
  3.                     verbose=0,
  4.                     cv=4,
  5.                     n_jobs=2)
  6. # Run the grid
  7. grid.fit(X_train, y_train)

接着我们查看相对结果最好的参数:

LightGBM使用简单介绍

接着我们打印出最好的结果,同时将最优的参数赋值到初始的dict中。

  1. print(grid.best_params_)
  2. print(grid.best_score_)
  3. """
  4. {'boosting_type': 'gbdt', 'colsample_bytree': 0.65, 'learning_rate': 0.005, 'n_estimators': 40, 'num_leaves': 16, 'objective': 'binary', 'random_state': 501, 'reg_alpha': 1.2, 'reg_lambda': 1, 'subsample': 0.75}
  5. 0.6068571428571429
  6. """

参数的赋值, 之后我们再训练分类的时候,就可以直接使用params这个dict了。

  1. # Using parameters already set above, replace in the best from the grid search
  2. params['colsample_bytree'] = grid.best_params_['colsample_bytree']
  3. params['learning_rate'] = grid.best_params_['learning_rate']
  4. # params['max_bin'] = grid.best_params_['max_bin']
  5. params['num_leaves'] = grid.best_params_['num_leaves']
  6. params['reg_alpha'] = grid.best_params_['reg_alpha']
  7. params['reg_lambda'] = grid.best_params_['reg_lambda']
  8. params['subsample'] = grid.best_params_['subsample']
  9. # params['subsample_for_bin'] = grid.best_params_['subsample_for_bin']

查看一下最终的效果:

  1. print('Fitting with params: ')
  2. print(params)
  3. """
  4. Fitting with params: 
  5. {'boosting_type': 'gbdt', 'max_depth': -1, 'objective': 'binary', 'nthread': 3, 'num_leaves': 16, 'learning_rate': 0.005, 'max_bin': 512, 'subsample_for_bin': 200, 'subsample': 0.75, 'subsample_freq': 1, 'colsample_bytree': 0.65, 'reg_alpha': 1.2, 'reg_lambda': 1, 'min_split_gain': 0.5, 'min_child_weight': 1, 'min_child_samples': 5, 'scale_pos_weight': 1, 'num_class': 1, 'metric': 'binary_error'}
  6. """

使用最优参数训练, 进行模型融合

在经过上面的步骤后,我们找到了最优的参数,下面我们使用这组最优的参数,进行模型的训练(每次训练使用不同的数据集), 最后将不同数据集训练的结果进行融合, 求出最优的解。

  1. # Kit k models with early-stopping on different training/validation splits
  2. k = 4
  3. predsValid = 0
  4. predsTrain = 0
  5. predsTest = 0
  6. for i in range(0, k):
  7.     print('Fitting model', k)
  8.     # Prepare the data set for fold
  9.     XX_train, XX_vali, yy_train, yy_vali = train_test_split(X_train, y_train,
  10.                                             test_size=0.4)
  11.     lgb_train = lgb.Dataset(XX_train, yy_train)
  12.     lgb_eval = lgb.Dataset(XX_vali, yy_vali, reference=lgb_train)
  13.     # Train
  14.     gbm = lgb.train(params,
  15.                     lgb_train,
  16.                     100000,
  17.                     valid_sets=[lgb_train, lgb_eval],
  18.                     early_stopping_rounds=50,
  19.                     verbose_eval=4)
  20.     # Plot importance
  21.     lgb.plot_importance(gbm)
  22.     plt.show()
  23.     # Predict
  24.     predsValid += gbm.predict(XX_train,
  25.                               num_iteration=gbm.best_iteration)/k
  26.     predsTrain += gbm.predict(XX_vali,
  27.                               num_iteration=gbm.best_iteration)/k
  28.     # 这里相当于是一个加权的过程
  29.     predsTest += gbm.predict(x_test,
  30.                              num_iteration=gbm.best_iteration)/k

可以看到每一轮的结束,都会打印出系数的重要度。

LightGBM使用简单介绍

模型的保存

最后进行模型的保存即可。

  1. pd.DataFrame(np.int32(predsTest > 0.5)).to_csv('sub.csv', header=None, index=None)

还是十分建议查看这一节开头给出的参考资料。

 

  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南
  • 本文由 发表于 2019年6月23日07:27:32
  • 转载请务必保留本文链接:https://mathpretty.com/10649.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: